mirror of
https://github.com/dec0dOS/zero-ui.git
synced 2024-09-20 06:56:05 +08:00
Merge pull request #148 from davidlag0/test/front_end_tests
Test/front end tests
This commit is contained in:
commit
be0cc0c80d
1
frontend/__tests__/__mocks__/fileMock.js
Normal file
1
frontend/__tests__/__mocks__/fileMock.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = "test-file-stub";
|
47
frontend/__tests__/unit/components/Bar.test.jsx
Normal file
47
frontend/__tests__/unit/components/Bar.test.jsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { render } from "@testing-library/react";
|
||||
import Bar from "components/Bar";
|
||||
import { Router } from "react-router-dom";
|
||||
import { createMemoryHistory } from "history";
|
||||
|
||||
// Useful reference: https://bholmes.dev/blog/mocking-browser-apis-fetch-localstorage-dates-the-easy-way-with-jest/
|
||||
|
||||
let mockStorage = {};
|
||||
|
||||
describe("Bar", () => {
|
||||
beforeAll(() => {
|
||||
global.Storage.prototype.getItem = jest.fn((key) => mockStorage[key]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// make sure the fridge starts out empty for each test
|
||||
mockStorage = {};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.Storage.prototype.getItem.mockReset();
|
||||
});
|
||||
|
||||
it("renders Bar unchanged when logged out", () => {
|
||||
const history = createMemoryHistory();
|
||||
mockStorage["loggedIn"] = false;
|
||||
|
||||
const { container } = render(
|
||||
<Router history={history}>
|
||||
<Bar />
|
||||
</Router>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders Bar unchanged when logged in", () => {
|
||||
const history = createMemoryHistory();
|
||||
mockStorage["loggedIn"] = true;
|
||||
|
||||
const { container } = render(
|
||||
<Router history={history}>
|
||||
<Bar />
|
||||
</Router>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
46
frontend/__tests__/unit/components/HomeLoggedIn.test.jsx
Normal file
46
frontend/__tests__/unit/components/HomeLoggedIn.test.jsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import HomeLoggedIn from "components/HomeLoggedIn";
|
||||
import { Router } from "react-router-dom";
|
||||
import { createMemoryHistory } from "history";
|
||||
import { testNetwork } from "../../data/network";
|
||||
import API from "utils/API";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
|
||||
describe("HomeLoggedIn", () => {
|
||||
it("renders HomeLoggedIn unchanged (no network)", async () => {
|
||||
const mock = new MockAdapter(API);
|
||||
const history = createMemoryHistory();
|
||||
|
||||
mock.onGet("network").reply(200, []);
|
||||
|
||||
const { container } = render(
|
||||
<Router history={history}>
|
||||
<HomeLoggedIn />
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/Please create at least one network/i)
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders HomeLoggedIn unchanged (with network)", async () => {
|
||||
const mock = new MockAdapter(API);
|
||||
const history = createMemoryHistory();
|
||||
|
||||
mock.onGet("network").reply(200, [testNetwork]);
|
||||
|
||||
const { container } = render(
|
||||
<Router history={history}>
|
||||
<HomeLoggedIn />
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(await screen.findByText(/0d303702cd0f1fc6/)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/new-net-11166/)).toBeInTheDocument();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
198
frontend/__tests__/unit/components/NetworkManagement.test.jsx
Normal file
198
frontend/__tests__/unit/components/NetworkManagement.test.jsx
Normal file
|
@ -0,0 +1,198 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import NetworkManagement from "components/NetworkManagement";
|
||||
import { MemoryRouter, Route, Router } from "react-router-dom";
|
||||
import { createMemoryHistory } from "history";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import API from "utils/API";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
|
||||
describe("NetworkManagement", () => {
|
||||
it("renders unchanged", () => {
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const { container } = render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("renders with initial state (accordion not expanded)", () => {
|
||||
const history = createMemoryHistory();
|
||||
|
||||
render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
expect(expandAccordionButton).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Delete Network" })
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test("renders with accordion expanded (and dialog closed)", async () => {
|
||||
const history = createMemoryHistory();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
expect(
|
||||
screen.getByRole("button", {
|
||||
name: "Delete Network",
|
||||
})
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("renders with accordion expanded and dialog opened", async () => {
|
||||
const history = createMemoryHistory();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
const deleteNetworkButton = screen.getByRole("button", {
|
||||
name: "Delete Network",
|
||||
});
|
||||
|
||||
await user.click(deleteNetworkButton);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Cancel" })).toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "Delete" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("renders with accordion closed after opening and closing it", async () => {
|
||||
const history = createMemoryHistory();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
expect(
|
||||
screen.getByRole("button", {
|
||||
name: "Delete Network",
|
||||
})
|
||||
).toBeVisible();
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
expect(
|
||||
screen.queryByRole("button", {
|
||||
name: "Delete Network",
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test("renders with accordion expanded and dialog closed (after opening and closing the dialog)", async () => {
|
||||
const history = createMemoryHistory();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Router history={history}>
|
||||
<NetworkManagement />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
const deleteNetworkButton = screen.getByRole("button", {
|
||||
name: "Delete Network",
|
||||
});
|
||||
|
||||
await user.click(deleteNetworkButton);
|
||||
|
||||
const dialogCancelButton = screen.getByRole("button", { name: "Cancel" });
|
||||
const dialogDeleteButton = screen.getByRole("button", { name: "Delete" });
|
||||
|
||||
expect(dialogCancelButton).toBeVisible();
|
||||
expect(dialogDeleteButton).toBeVisible();
|
||||
|
||||
await user.click(dialogCancelButton);
|
||||
|
||||
expect(dialogCancelButton).not.toBeVisible();
|
||||
expect(dialogDeleteButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("renders with network deleted (after expanding the acordion, opening the dialog and clicking the button to delete the network)", async () => {
|
||||
const mock = new MockAdapter(API);
|
||||
const user = userEvent.setup();
|
||||
const nwid = "123";
|
||||
let testLocation;
|
||||
|
||||
mock.onDelete("/network/" + nwid).reply(200);
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/network/" + nwid]}>
|
||||
<Route path="/network/:nwid">
|
||||
<NetworkManagement />
|
||||
</Route>
|
||||
<Route
|
||||
path="*"
|
||||
render={({ location }) => {
|
||||
testLocation = location;
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const expandAccordionButton = screen.getByRole("button", {
|
||||
name: "Management",
|
||||
});
|
||||
|
||||
await user.click(expandAccordionButton);
|
||||
|
||||
const deleteNetworkButton = screen.getByRole("button", {
|
||||
name: "Delete Network",
|
||||
});
|
||||
|
||||
await user.click(deleteNetworkButton);
|
||||
|
||||
const dialogCancelButton = screen.getByRole("button", { name: "Cancel" });
|
||||
const dialogDeleteButton = screen.getByRole("button", { name: "Delete" });
|
||||
|
||||
expect(dialogCancelButton).toBeVisible();
|
||||
expect(dialogDeleteButton).toBeVisible();
|
||||
|
||||
await user.click(dialogDeleteButton);
|
||||
expect(testLocation.pathname).toBe("/");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Bar renders Bar unchanged when logged in 1`] = `
|
||||
<div>
|
||||
<header
|
||||
class="MuiPaper-root MuiAppBar-root MuiAppBar-positionStatic MuiAppBar-colorSecondary MuiPaper-elevation4"
|
||||
style="background: rgb(0, 0, 0);"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular MuiToolbar-gutters"
|
||||
>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-2"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-h6 MuiTypography-colorInherit"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineNone MuiTypography-colorInherit"
|
||||
href="/"
|
||||
>
|
||||
<img
|
||||
alt="logo"
|
||||
height="100"
|
||||
src="test-file-stub"
|
||||
width="100"
|
||||
/>
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Bar renders Bar unchanged when logged out 1`] = `
|
||||
<div>
|
||||
<header
|
||||
class="MuiPaper-root MuiAppBar-root MuiAppBar-positionStatic MuiAppBar-colorSecondary MuiPaper-elevation4"
|
||||
style="background: rgb(0, 0, 0);"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular MuiToolbar-gutters"
|
||||
>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-1"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-h6 MuiTypography-colorInherit"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineNone MuiTypography-colorInherit"
|
||||
href="/"
|
||||
>
|
||||
<img
|
||||
alt="logo"
|
||||
height="100"
|
||||
src="test-file-stub"
|
||||
width="100"
|
||||
/>
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Log In
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,145 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HomeLoggedIn renders HomeLoggedIn unchanged (no network) 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="makeStyles-root-1"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-createBtn-2 MuiButton-containedPrimary"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Create A Network
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<hr
|
||||
class="MuiDivider-root"
|
||||
/>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-container-3 MuiGrid-container MuiGrid-spacing-xs-3"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Controller networks
|
||||
</h5>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-4"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-auto"
|
||||
>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1"
|
||||
>
|
||||
Networks
|
||||
</p>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<div>
|
||||
Please create at least one network
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`HomeLoggedIn renders HomeLoggedIn unchanged (with network) 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="makeStyles-root-5"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-createBtn-6 MuiButton-containedPrimary"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Create A Network
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<hr
|
||||
class="MuiDivider-root"
|
||||
/>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-container-7 MuiGrid-container MuiGrid-spacing-xs-3"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Controller networks
|
||||
</h5>
|
||||
Network controller address
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-8"
|
||||
>
|
||||
0d303702cd
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-auto"
|
||||
>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1"
|
||||
>
|
||||
Networks
|
||||
</p>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<div
|
||||
class="netBtn"
|
||||
role="button"
|
||||
>
|
||||
<a
|
||||
class="makeStyles-link-9"
|
||||
href="/network/0d303702cd0f1fc6"
|
||||
>
|
||||
<ul
|
||||
class="MuiList-root makeStyles-flexContainer-10 MuiList-padding"
|
||||
>
|
||||
<li
|
||||
class="MuiListItem-root makeStyles-nwid-12 MuiListItem-gutters"
|
||||
>
|
||||
0d303702cd0f1fc6
|
||||
</li>
|
||||
<li
|
||||
class="MuiListItem-root makeStyles-name-11 MuiListItem-gutters"
|
||||
>
|
||||
new-net-11166
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,99 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NetworkManagement renders unchanged 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MuiPaper-root MuiAccordion-root MuiAccordion-rounded MuiPaper-elevation1 MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
class="MuiButtonBase-root MuiAccordionSummary-root"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiAccordionSummary-content"
|
||||
>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1"
|
||||
>
|
||||
Management
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-hidden="true"
|
||||
class="MuiButtonBase-root MuiIconButton-root MuiAccordionSummary-expandIcon MuiIconButton-edgeEnd"
|
||||
>
|
||||
<span
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiCollapse-root MuiCollapse-hidden"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<div
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="MuiAccordionDetails-root"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedSecondary"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-startIcon MuiButton-iconSizeMedium"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
Delete Network
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -24,7 +24,8 @@ const customJestConfig = {
|
|||
moduleNameMapper: {
|
||||
"^uuid$": require.resolve("uuid"),
|
||||
"^@fontsource/roboto$": "identity-obj-proxy",
|
||||
"\\.(png|css)$": "identity-obj-proxy",
|
||||
"\\.(css)$": "identity-obj-proxy",
|
||||
"\\.(png|pdf|svg|jpg|jpeg)$": "<rootDir>/__tests__/__mocks__/fileMock.js",
|
||||
},
|
||||
testPathIgnorePatterns: ["<rootDir>/cypress/"],
|
||||
};
|
||||
|
|
|
@ -23,12 +23,14 @@
|
|||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"axios-mock-adapter": "^1.21.2",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"jest": "26.6.0",
|
||||
"jest-transform-css": "^6.0.0",
|
||||
"postcss": "^8.4.21",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
Loading…
Reference in a new issue