Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ module.exports = {
},
],
],
testEnvironment: "jsdom",
testEnvironment: "<rootDir>/tests/jest-environment.js",
modulePathIgnorePatterns: ["<rootDir>/dist/"],
};
10,405 changes: 4,466 additions & 5,939 deletions package-lock.json

Large diffs are not rendered by default.

50 changes: 28 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
"description": "React admin frontend for Django.",
"main": "index.js",
"private": true,
"keywords": ["admin", "bananas", "django", "django-admin", "react"],
"keywords": [
"admin",
"bananas",
"django",
"django-admin",
"react"
],
"scripts": {
"start": "webpack-dev-server --config app/webpack.config.js",
"watch": "jest --watchAll --verbose",
Expand All @@ -25,26 +31,26 @@
},
"dependencies": {
"@date-io/date-fns": "^2.17.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/x-date-pickers": "^5.0.20",
"classnames": "^2.5.1",
"date-fns": "^2.23.0",
"history": "^4.10.1",
"js-logger": "^1.6.1",
"lodash": "^4.17.21",
"prop-types": "^15.8.1",
"swagger-client": "3.8.1"
"swagger-client": "3.36.0"
},
"peerDependencies": {
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@mui/styles": "^5.15.10",
"final-form": "^4.20.10",
"final-form-arrays": "^3.1.0",
"@mui/icons-material": "^5.18.0",
"@mui/material": "^5.18.0",
"@mui/styles": "^5.18.0",
"final-form": "^5.0.0",
"final-form-arrays": "^4.0.0",
"react": "^18.2.0",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4"
"react-final-form": "^7.0.0",
"react-final-form-arrays": "^4.0.0"
},
"devDependencies": {
"@babel/cli": "7.23.9",
Expand All @@ -55,9 +61,9 @@
"@babel/preset-env": "7.23.9",
"@babel/preset-react": "7.23.3",
"@date-io/core": "1.3.8",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@mui/styles": "^5.15.10",
"@mui/icons-material": "^5.18.0",
"@mui/material": "^5.18.0",
"@mui/styles": "^5.18.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "14.2.1",
Expand All @@ -74,22 +80,22 @@
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "12.0.0",
"fetch-mock": "9.11.0",
"fetch-mock": "^12.6.0",
"file-loader": "6.2.0",
"final-form": "4.20.10",
"final-form-arrays": "3.1.0",
"gh-pages": "2.0.1",
"final-form": "^5.0.0",
"final-form-arrays": "^4.0.0",
"gh-pages": "^6.3.0",
"gravatar": "1.8.2",
"html-webpack-plugin": "5.3.2",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.2.5",
"react": "^18.2.0",
"react-dom": "18.2.0",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4",
"webpack": "5.90.3",
"react-final-form": "^7.0.0",
"react-final-form-arrays": "^4.0.0",
"webpack": "^5.103.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.2"
"webpack-dev-server": "^5.2.2"
}
}
9 changes: 6 additions & 3 deletions tests/api.mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import anonymSchema from "./schema.anonymous.json";
import authedSchema from "./schema.authenticated.json";

const schemaUrl = "http://foo.bar/api/v1.0/schema.json";
fetchMock.config.overwriteRoutes = true;

export const user = {
id: 1,
Expand All @@ -18,10 +17,14 @@ export const user = {
};

export function mockAPI({ anonymous, schema } = {}) {
// Clear existing routes and set up global mock
fetchMock.removeRoutes().clearHistory().mockGlobal();

// Mock Schema
fetchMock.mock(
fetchMock.route(
schemaUrl,
schema || (anonymous ? anonymSchema : authedSchema)
schema || (anonymous ? anonymSchema : authedSchema),
"schema"
);

const translations = {
Expand Down
13 changes: 8 additions & 5 deletions tests/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import fetchMock from "fetch-mock";

import getAPIClient from "./api.mock";

const { Response } = fetchMock.config;
const nofAPIEndpoints = 17;

test("Client is NOT authenticated", async () => {
Expand Down Expand Up @@ -34,7 +33,7 @@ test("Operations has attached OPTIONS method call", async () => {
const client = await getAPIClient();
const operation = client.operations["example.user:list"];

fetchMock.once("http://foo.bar/api/v1.0/example/user/", "{}");
fetchMock.route("http://foo.bar/api/v1.0/example/user/", "{}", { repeat: 1 });

await expect(operation.options()).resolves.toMatchObject({ ok: true });
});
Expand All @@ -57,9 +56,10 @@ test("Can subscribe to progress events", async () => {
progressHandler: progress,
});

fetchMock.once(
fetchMock.route(
"http://foo.bar/api/v1.0/bananas/logout/",
new Response("", { status: 204 })
{ status: 200, body: "" },
{ repeat: 1 }
);

await client.operations["bananas.logout:create"]();
Expand Down Expand Up @@ -93,7 +93,10 @@ test("Can handle unreachable endpoint", async () => {
errorHandler: error,
});

fetchMock.post("http://foo.bar/api/v1.0/bananas/logout/", 523);
// Simulate a network error (unreachable endpoint)
fetchMock.post("http://foo.bar/api/v1.0/bananas/logout/", {
throws: new TypeError("Failed to fetch"),
});

await expect(client.operations["bananas.logout:create"]()).rejects.toThrow();

Expand Down
43 changes: 29 additions & 14 deletions tests/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const renderApp = async ({ anonymous = false, props = {} } = {}) => {
afterEach(() => {
// Make sure that we always start on the dashboard page
window.history.pushState({}, "", "/");
fetchMock.reset();
fetchMock.removeRoutes().clearHistory();
cleanup();
});

Expand Down Expand Up @@ -124,7 +124,7 @@ test("Can render dashboard and navigate using menu", async () => {
{ id: 1, username: "user1" },
{ id: 2, username: "user2" },
];
fetchMock.mock(`http://foo.bar/api/v1.0${userListRoute.path}`, {
fetchMock.route(`http://foo.bar/api/v1.0${userListRoute.path}`, {
body: users,
});

Expand Down Expand Up @@ -391,21 +391,29 @@ test("Can change password", async () => {
await waitFor(() => expect(submitButton).toBeEnabled());

// Mock fail endpoint and click submit
fetchMock.post("http://foo.bar/api/v1.0/bananas/change_password/", {
status: 400,
body: {},
});
fetchMock.post(
"http://foo.bar/api/v1.0/bananas/change_password/",
{
status: 400,
body: {},
},
{ name: "change-password", repeat: 1 }
);
// TODO: Click button instead of submiting form; await userEvent.click(submitButton);
fireEvent.submit(form);

// Expect error message to show
await waitFor(() => getByText("Incorrect authentication credentials."));

// Mock success endpoint and click submit, again
fetchMock.post("http://foo.bar/api/v1.0/bananas/change_password/", {
status: 204,
body: "",
});
fetchMock.post(
"http://foo.bar/api/v1.0/bananas/change_password/",
{
status: 200,
body: "",
},
{ name: "change-password-success", repeat: 1 }
);
fireEvent.submit(form);

// Expect success message to show
Expand All @@ -417,7 +425,7 @@ test("A hash change will trigger rerender", async () => {

// Mock Users API call
const userListRoute = app.router.getRoute("example.user:list");
fetchMock.mock(`http://foo.bar/api/v1.0${userListRoute.path}`, { body: [] });
fetchMock.route(`http://foo.bar/api/v1.0${userListRoute.path}`, { body: [] });

act(() => app.router.reroute({ id: "example.user:list" }));
await waitFor(() => getByText("Hash: none"), { container });
Expand Down Expand Up @@ -493,9 +501,16 @@ test("Can customize HTTP headers", async () => {
},
});

expect(fetchMock.called()).toBe(true);
fetchMock.calls().forEach(([, options]) => {
expect(options.headers.Authorization).toBe("secret");
expect(fetchMock.callHistory.called()).toBe(true);
fetchMock.callHistory.calls().forEach(call => {
// In fetch-mock v12, headers may be stored with lowercase keys
const headers = call.options?.headers || {};
// Try both lowercase and capitalized versions since HTTP headers are case-insensitive
const authHeader =
headers instanceof Headers
? headers.get("Authorization")
: headers.Authorization || headers.authorization;
expect(authHeader).toBe("secret");
});
});

Expand Down
4 changes: 2 additions & 2 deletions tests/forms/Form.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test("Ensure the default 'onSubmit' is firing a correct request", async () => {

await waitFor(() => success.calls);
expect(success).toHaveBeenCalledWith("Changes have been saved!");
expect(fetchMock.called(matcher)).toBe(true);
expect(fetchMock.callHistory.called(matcher)).toBe(true);
});

test("Ensure the default 'onSubmit' can handle errors", async () => {
Expand Down Expand Up @@ -63,7 +63,7 @@ test("Ensure the default 'onSubmit' can handle errors", async () => {

await waitFor(() => error.calls);
expect(error).toHaveBeenCalledWith("Please correct the errors on this form.");
expect(fetchMock.called(matcher)).toBe(true);
expect(fetchMock.callHistory.called(matcher)).toBe(true);
expect(queryByText("bazrror")).not.toBeNull();
});

Expand Down
Loading