Skip to content

Commit

Permalink
Merge 8db9936 into af0b39b
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Feb 10, 2022
2 parents af0b39b + 8db9936 commit 155142d
Show file tree
Hide file tree
Showing 18 changed files with 718 additions and 101 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CI

on: [push, pull_request]

jobs:
build:

runs-on: ubuntu-latest

defaults:
run:
working-directory: ./web

strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: 'web/package-lock.json'

- name: Install dependencies
run: npm install

# - name: Build the application
# run: npm run build --if-present

- name: Run the tests and generate coverage report
run: npm test \"src/**/*.test.js\" -- --coverage

- name: Coveralls GitHub Action
uses: coverallsapp/github-action@1.1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
base-path: ./web
path-to-lcov: ./web/coverage/lcov.info
flag-name: Unit
10 changes: 10 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,15 @@
"devDependencies": {
"http-proxy-middleware": "^2.0.2",
"prettier": "^2.5.1"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!src/lib/cockpit.js",
"!src/reportWebVitals.js",
"!src/serviceWorker.js",
"!src/setupProxy.js",
"!<rootDir>/node_modules/"
]
}
}
53 changes: 48 additions & 5 deletions web/src/App.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
import { screen } from "@testing-library/react";
import { render } from "./test-utils";
import userEvent from "@testing-library/user-event";
import { authRender } from "./test-utils";
import App from "./App";
import InstallerClient from "./lib/InstallerClient";

test("renders the App", () => {
render(<App />);
const title = screen.getByText(/Welcome to D-Installer/i);
expect(title).toBeInTheDocument();
jest.mock("./lib/InstallerClient");
jest.mock("./Installer", () => {
return {
__esModule: true,
default: () => {
return <div>Installer Component</div>;
}
};
});

describe("when the user is already logged in", () => {
beforeEach(() => {
InstallerClient.mockImplementation(() => {
return {
authorize: (_username, _password) => Promise.resolve(false),
isLoggedIn: () => Promise.resolve(true),
currentUser: () => Promise.resolve("jane")
};
});
});

it("shows the installer", async () => {
authRender(<App />);
await screen.findByText("Installer Component");
});
});

describe("when username and password are wrong", () => {
beforeEach(() => {
InstallerClient.mockImplementation(() => {
return {
authorize: () => Promise.reject("password does not match"),
isLoggedIn: () => Promise.resolve(false),
onSignal: jest.fn()
};
});
});

it("shows an error", async () => {
authRender(<App />);
userEvent.type(screen.getByLabelText(/Username/i), "john");
userEvent.type(screen.getByLabelText(/Password/i), "something");
userEvent.click(screen.getByRole("button", { name: /Login/ }));
await screen.findByText(/Authentication failed/i);
});
});
1 change: 1 addition & 0 deletions web/src/LanguageSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default function LanguageSelector() {

return (
<FormSelect
id="language"
value={language}
onChange={v => dispatch({ type: "CHANGE", payload: v })}
aria-label="language"
Expand Down
54 changes: 54 additions & 0 deletions web/src/LanguageSelector.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { installerRender } from "./test-utils";
import LanguageSelector from "./LanguageSelector";
import InstallerClient from "./lib/InstallerClient";

jest.mock("./lib/InstallerClient");

const languages = [
{ id: "en_US", name: "English" },
{ id: "de_DE", name: "German" }
];

const clientMock = {
getLanguages: () => Promise.resolve(languages),
getOption: () => Promise.resolve("en_US")
};

beforeEach(() => {
InstallerClient.mockImplementation(() => clientMock);
});

it("displays the proposal", async () => {
installerRender(<LanguageSelector />);
await screen.findByText("English");
});

describe("when the user changes the language", () => {
let setOptionFn;

beforeEach(() => {
// if defined outside, the mock is cleared automatically
setOptionFn = jest.fn().mockResolvedValue();
InstallerClient.mockImplementation(() => {
return {
...clientMock,
setOption: setOptionFn
};
});
});

it("changes the selected language", async () => {
installerRender(<LanguageSelector />);
const button = await screen.findByRole("button", { name: "English" });
userEvent.click(button);

const languageSelector = await screen.findByLabelText("Select language");
userEvent.selectOptions(languageSelector, ["German"]);
userEvent.click(screen.getByRole("button", { name: "Confirm" }));

await screen.findByRole("button", { name: "German" });
expect(setOptionFn).toHaveBeenCalledWith("Language", "de_DE");
});
});
8 changes: 7 additions & 1 deletion web/src/LoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import {
Form,
FormAlert,
FormGroup,
TextInput
TextInput,
TextContent,
Text,
TextVariants
} from "@patternfly/react-core";

const formError = error => (
Expand All @@ -55,6 +58,9 @@ function LoginForm() {
return (
<Bullseye>
<Form>
<TextContent>
<Text component={TextVariants.h1}>Welcome to D-Installer</Text>
</TextContent>
{error && formError(error)}
<FormGroup label="Username" fieldId="username">
<TextInput isRequired type="text" id="username" ref={usernameRef} />
Expand Down
2 changes: 1 addition & 1 deletion web/src/Overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function Overview() {
<Stack hasGutter>
<StackItem>
<TextContent>
<Text component={TextVariants.h1}>Welcome to D-Installer</Text>
<Text component={TextVariants.h1}>Installation Overview</Text>
</TextContent>
</StackItem>

Expand Down
55 changes: 51 additions & 4 deletions web/src/Overview.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,56 @@
import { screen } from "@testing-library/react";
import { render } from "./test-utils";
import userEvent from "@testing-library/user-event";
import { installerRender } from "./test-utils";
import Overview from "./Overview";
import InstallerClient from "./lib/InstallerClient";

test("renders the Overview", () => {
render(<Overview />);
const title = screen.getByText(/Welcome to D-Installer/i);
jest.mock("./lib/InstallerClient");

const proposal = [
{ mount: "/", type: "Btrfs", device: "/dev/sda1", size: 100000000 },
{ mount: "/home", type: "Ext4", device: "/dev/sda2", size: 10000000000 }
];
const disks = [{ name: "/dev/sda" }, { name: "/dev/sdb" }];
const languages = [{ id: "en_US", name: "English" }];
const products = [{ name: "openSUSE", display_name: "openSUSE Tumbleweed" }];
const options = {
Language: "en_US",
Product: "openSUSE",
Disk: "/dev/sda"
};
const startInstallationFn = jest.fn();

beforeEach(() => {
InstallerClient.mockImplementation(() => {
return {
getStorage: () => Promise.resolve(proposal),
getDisks: () => Promise.resolve(disks),
getLanguages: () => Promise.resolve(languages),
getProducts: () => Promise.resolve(products),
getOption: name => Promise.resolve(options[name]),
onPropertyChanged: jest.fn(),
startInstallation: startInstallationFn
};
});
});

test("renders the Overview", async () => {
installerRender(<Overview />);
const title = screen.getByText(/Installation Overview/i);
expect(title).toBeInTheDocument();

await screen.findByText("English");
await screen.findByText("/home");
await screen.findByText("openSUSE Tumbleweed");
});

test("starts the installation when the user clicks 'Install'", async () => {
installerRender(<Overview />);

// TODO: we should have some UI element to tell the user we have finished
// with loading data.
await screen.findByText("English");

userEvent.click(screen.getByRole("button", { name: /Install/ }));
expect(startInstallationFn).toHaveBeenCalled();
});
1 change: 1 addition & 0 deletions web/src/ProductSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default function ProductSelector() {

return (
<FormSelect
id="product"
value={product}
onChange={v => dispatch({ type: "CHANGE", payload: v })}
aria-label="product"
Expand Down
56 changes: 56 additions & 0 deletions web/src/ProductSelector.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { installerRender } from "./test-utils";
import ProductSelector from "./ProductSelector";
import InstallerClient from "./lib/InstallerClient";

jest.mock("./lib/InstallerClient");

const products = [
{ name: "openSUSE", display_name: "openSUSE Tumbleweed" },
{ name: "micro", display_name: "openSUSE MicroOS" }
];

const clientMock = {
getProducts: () => Promise.resolve(products),
getOption: () => Promise.resolve("micro")
};

beforeEach(() => {
InstallerClient.mockImplementation(() => clientMock);
});

it("displays the proposal", async () => {
installerRender(<ProductSelector />);
await screen.findByText("openSUSE MicroOS");
});

describe("when the user changes the product", () => {
let setOptionFn;

beforeEach(() => {
// if defined outside, the mock is cleared automatically
setOptionFn = jest.fn().mockResolvedValue();
InstallerClient.mockImplementation(() => {
return {
...clientMock,
setOption: setOptionFn
};
});
});

it("changes the selected product", async () => {
installerRender(<ProductSelector />);
const button = await screen.findByRole("button", {
name: "openSUSE MicroOS"
});
userEvent.click(button);

const productSelector = await screen.findByLabelText(/Select the product/);
userEvent.selectOptions(productSelector, ["openSUSE Tumbleweed"]);
userEvent.click(screen.getByRole("button", { name: "Confirm" }));

await screen.findByRole("button", { name: "openSUSE Tumbleweed" });
expect(setOptionFn).toHaveBeenCalledWith("Product", "openSUSE");
});
});
4 changes: 2 additions & 2 deletions web/src/Storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ export default function Storage() {
}, []);

return (
<div>
<>
<TargetSelector
target={target || "Select target"}
targets={targets}
onAccept={onAccept}
/>
<Proposal data={proposal} />
</div>
</>
);
}

0 comments on commit 155142d

Please sign in to comment.