Skip to content

Commit

Permalink
SLVUU-117 guard for LayoutJSON and unit tests for Provider
Browse files Browse the repository at this point in the history
  • Loading branch information
vferraro-scottlogic committed Jan 11, 2024
1 parent da8109e commit cd66125
Show file tree
Hide file tree
Showing 7 changed files with 1,129 additions and 294 deletions.
1,187 changes: 902 additions & 285 deletions vuu-ui/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions vuu-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
},
"dependencies": {
"@salt-ds/core": "1.13.2",
"@types/jest": "^26.0.20",
"@testing-library/react-hooks": "^8.0.1",
"@types/jest": "^29.5.11",
"@types/node": "^18.0.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
Expand Down Expand Up @@ -77,7 +78,7 @@
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"happy-dom": "^10.10.0",
"happy-dom": "^12.10.3",
"open": "10.0.0",
"prettier": "2.8.4",
"serve": "^14.2.1",
Expand Down
4 changes: 3 additions & 1 deletion vuu-ui/packages/vuu-layout/src/utils/typeOf.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactElement } from 'react';
import { LayoutModel, WithType } from '../layout-reducer';
import { LayoutJSON, LayoutModel, WithType } from '../layout-reducer';

export function typeOf(element?: LayoutModel | WithType): string | undefined {
if (element) {
Expand All @@ -23,3 +23,5 @@ export function typeOf(element?: LayoutModel | WithType): string | undefined {
}

export const isTypeOf = (element: ReactElement, type: string) => typeOf(element) === type;

export const isLayoutJSON = (layout: LayoutJSON): layout is LayoutJSON=> "type" in layout;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
LayoutJSON,
resolveJSONPath,
ApplicationSetting,
isLayoutJSON,
} from "@finos/vuu-layout";
import { NotificationLevel, useNotifications } from "@finos/vuu-popups";
import { LayoutMetadata, LayoutMetadataDto } from "./layoutTypes";
Expand Down Expand Up @@ -164,8 +165,12 @@ export const LayoutManagementProvider = (

const saveApplicationLayout = useCallback(
(layout: LayoutJSON) => {
setApplicationLayout(layout, false);
getPersistenceManager().saveApplicationJSON(applicationJSONRef.current);
if (isLayoutJSON(layout)) {
setApplicationLayout(layout, false);
getPersistenceManager().saveApplicationJSON(applicationJSONRef.current);
} else {
console.error("Tried to save invalid application layout", layout);
}
},
[setApplicationLayout]
);
Expand All @@ -177,7 +182,7 @@ export const LayoutManagementProvider = (
"#main-tabs.ACTIVE_CHILD"
);

if (layoutToSave) {
if (layoutToSave && isLayoutJSON(layoutToSave)) {
getPersistenceManager()
.createLayout(metadata, ensureLayoutHasTitle(layoutToSave, metadata))
.then((metadata) => {
Expand All @@ -197,10 +202,11 @@ export const LayoutManagementProvider = (
console.error("Error occurred while saving layout", error);
});
} else {
console.error("Tried to save invalid layout", layoutToSave);
notify({
type: NotificationLevel.Error,
header: "Failed to Save Layout",
body: "Cannot save undefined layout",
body: "Cannot save invalid layout",
});
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React from "react";
import "@finos/vuu-layout/test/global-mocks";
import { renderHook } from "@testing-library/react-hooks";
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";

import { LayoutJSON, isLayoutJSON, resolveJSONPath } from "@finos/vuu-layout";

import { useNotifications } from "@finos/vuu-popups";

import {
LayoutManagementProvider,
LayoutMetadata,
useLayoutManager,
} from "../../src";

import {
LocalPersistenceManager,
PersistenceManager,
} from "../../src/persistence-management";

const defaultLayout = vi.hoisted(() => ({
type: "Stack",
title: "test-layout",
}));

const newLayout: LayoutJSON = {
type: "Stack",
title: "test-layout-edited",
};

const metadata: LayoutMetadata = {
name: "test-name",
group: "test-group",
screenshot: "test-screenshot",
user: "test-user",
created: "test-date",
id: "test-id",
};

const initialApplicationJSON = vi.hoisted(() => ({
layout: defaultLayout,
}));

vi.stubEnv("LOCAL", "true");

vi.mock(
"../../src/persistence-management/LocalPersistenceManager",
async () => {
const MockPersistenceManager = vi.fn();
MockPersistenceManager.prototype.loadMetadata = vi.fn();
MockPersistenceManager.prototype.loadApplicationJSON = vi.fn();
MockPersistenceManager.prototype.saveApplicationJSON = vi.fn();
MockPersistenceManager.prototype.createLayout = vi.fn();
MockPersistenceManager.prototype.updateLayout = vi.fn();
MockPersistenceManager.prototype.deleteLayout = vi.fn();
MockPersistenceManager.prototype.loadLayout = vi.fn();

return { LocalPersistenceManager: MockPersistenceManager };
}
);

vi.mock("../../src/persistence-management/defaultApplicationJson", async () => {
const actual = await vi.importActual<
typeof import("../../src/persistence-management/defaultApplicationJson")
>("../../src/persistence-management/defaultApplicationJson");
return {
...actual,
loadingApplicationJson: initialApplicationJSON,
};
});

vi.mock("@finos/vuu-popups", async () => {
const actual = await vi.importActual<typeof import("@finos/vuu-popups")>(
"@finos/vuu-popups"
);
const mockNotify = vi.fn();
return {
...actual,
useNotifications: vi.fn(() => ({ notify: mockNotify })),
};
});

vi.mock("@finos/vuu-layout", async () => {
const actual = await vi.importActual<typeof import("@finos/vuu-layout")>(
"@finos/vuu-layout"
);
return {
...actual,
isLayoutJSON: vi.fn(),
resolveJSONPath: vi.fn(),
};
});

const wrapper = ({ children }) => (
<LayoutManagementProvider>{children}</LayoutManagementProvider>
);

describe("LayoutManagementProvider", () => {
let persistence: PersistenceManager;

beforeEach(() => {
persistence = new LocalPersistenceManager();
vi.mocked(persistence.loadMetadata).mockResolvedValueOnce([]);
vi.mocked(persistence.loadApplicationJSON).mockResolvedValueOnce(
initialApplicationJSON
);
vi.spyOn(global.console, "error");
});

afterEach(() => {
vi.restoreAllMocks();
});

it("calls loadMetadata and loadApplicationJSON on mount", () => {
const { result } = renderHook(() => useLayoutManager(), { wrapper });

expect(persistence.loadMetadata).toHaveBeenCalled();
expect(persistence.loadApplicationJSON).toHaveBeenCalled();
expect(result.current.applicationJson).toBe(initialApplicationJSON);
});

describe("saveApplicationLayout", () => {
it("calls saveApplicationJSON when layout is valid", () => {
const { result } = renderHook(useLayoutManager, { wrapper });

vi.mocked(persistence.saveApplicationJSON).mockResolvedValueOnce();
vi.mocked(isLayoutJSON).mockReturnValue(true);

result.current.saveApplicationLayout(newLayout);

expect(persistence.saveApplicationJSON).toHaveBeenCalledWith({
...initialApplicationJSON,
layout: newLayout,
});
});

it("doesn't call saveApplicationJSON and logs error when layout is not valid ", () => {
const { result } = renderHook(() => useLayoutManager(), { wrapper });

vi.mocked(persistence.saveApplicationJSON).mockResolvedValueOnce();
vi.mocked(isLayoutJSON).mockReturnValue(false);

result.current.saveApplicationLayout(newLayout);

expect(persistence.saveApplicationJSON).not.toHaveBeenCalled();
expect(console.error).toHaveBeenCalledWith(
"Tried to save invalid application layout",
newLayout
);
});
});

describe("saveLayout", () => {
it("calls createLayout when layout is valid and path is resolved", () => {
const { result } = renderHook(() => useLayoutManager(), { wrapper });

vi.mocked(persistence.createLayout).mockResolvedValueOnce(metadata);
vi.mocked(isLayoutJSON).mockReturnValue(true);
vi.mocked(resolveJSONPath).mockReturnValue(newLayout);

result.current.saveLayout(metadata);

expect(persistence.createLayout).toHaveBeenCalled();
});

it("doesn't call createLayout, triggers error notification and logs error when layout path can't be resolved ", () => {
const { result } = renderHook(() => useLayoutManager(), { wrapper });
const { notify } = useNotifications();

vi.mocked(persistence.createLayout).mockResolvedValueOnce(metadata);
vi.mocked(resolveJSONPath).mockReturnValue(undefined);

result.current.saveLayout(metadata);

expect(persistence.createLayout).not.toHaveBeenCalled();
expect(notify).toHaveBeenCalledWith({
body: "Cannot save invalid layout",
header: "Failed to Save Layout",
type: "error",
});
expect(console.error).toHaveBeenCalledWith(
"Tried to save invalid layout",
undefined
);
});

it("doesn't call createLayout, triggers error notification and logs error when layout is not valid", () => {
const { result } = renderHook(() => useLayoutManager(), { wrapper });
const { notify } = useNotifications();

vi.mocked(persistence.createLayout).mockResolvedValueOnce(metadata);
vi.mocked(isLayoutJSON).mockReturnValue(false);
vi.mocked(resolveJSONPath).mockReturnValue(defaultLayout);

result.current.saveLayout(metadata);

expect(persistence.createLayout).not.toHaveBeenCalled();
expect(notify).toHaveBeenCalledWith({
body: "Cannot save invalid layout",
header: "Failed to Save Layout",
type: "error",
});
expect(console.error).toHaveBeenCalledWith(
"Tried to save invalid layout",
defaultLayout
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LayoutJSON } from "@finos/vuu-layout";
import {
getLocalEntity,
saveLocalEntity,
} from "../../../vuu-filters/src/local-config";
} from "@finos/vuu-filters/src/local-config";
import { formatDate } from "@finos/vuu-utils";
import { expectPromiseRejectsWithError } from "@finos/vuu-utils/test/utils";
import { LocalPersistenceManager } from "../../src/persistence-management/LocalPersistenceManager";
Expand Down
2 changes: 1 addition & 1 deletion vuu-ui/vitest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
include: ["packages/**/test/**/**.test.(js|ts)"],
include: ["packages/**/test/**/**.test.(js|ts|tsx)"],
environment: 'happy-dom',
},
});

0 comments on commit cd66125

Please sign in to comment.