Skip to content

Commit 936a8d9

Browse files
committed
Added mocha testing to inventory-app using jsdom
1 parent d3819c8 commit 936a8d9

File tree

6 files changed

+186
-2
lines changed

6 files changed

+186
-2
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
"use strict";
7+
8+
const getFluidTestMochaConfig = require("@fluid-internal/mocha-test-setup/mocharc-common");
9+
10+
const config = getFluidTestMochaConfig(__dirname);
11+
// TODO: figure out why this package needs the --exit flag, tests might not be cleaning up correctly after themselves.
12+
// AB#7856
13+
config.exit = true;
14+
15+
module.exports = config;

examples/data-objects/inventory-app/package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"build": "fluid-build . --task build",
1717
"build:compile": "fluid-build . --task compile",
1818
"build:esnext": "tsc --project ./tsconfig.json",
19+
"build:test": "tsc --project ./src/test/tsconfig.json",
1920
"check:biome": "biome check .",
2021
"check:format": "npm run check:biome",
2122
"clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\" nyc",
@@ -33,7 +34,9 @@
3334
"start:spo-df": "webpack serve --config webpack.config.cjs --env mode=spo-df",
3435
"start:tinylicious": "webpack serve --config webpack.config.cjs --env mode=tinylicious",
3536
"webpack": "webpack --env production",
36-
"webpack:dev": "webpack --env development"
37+
"webpack:dev": "webpack --env development",
38+
"test": "npm run test:mocha",
39+
"test:mocha": "mocha lib/test/**/*.test.js"
3740
},
3841
"dependencies": {
3942
"@fluid-example/example-utils": "workspace:~",
@@ -45,14 +48,22 @@
4548
"devDependencies": {
4649
"@biomejs/biome": "~1.9.3",
4750
"@fluid-example/webpack-fluid-loader": "workspace:~",
51+
"@fluid-internal/mocha-test-setup": "workspace:~",
52+
"@fluidframework/fluid-static": "workspace:~",
53+
"@fluidframework/tinylicious-client": "workspace:~",
4854
"@fluidframework/build-common": "^2.0.3",
4955
"@fluidframework/build-tools": "^0.62.0",
5056
"@fluidframework/eslint-config-fluid": "workspace:~",
57+
"@testing-library/react": "^16.3.0",
58+
"@types/mocha": "^10.0.10",
59+
"@types/node": "^18.19.0",
5160
"@types/react": "^18.3.11",
61+
"global-jsdom": "^26.0.0",
5262
"eslint": "~9.39.1",
5363
"eslint-plugin-react": "~7.37.5",
5464
"eslint-plugin-react-hooks": "~7.0.1",
5565
"jiti": "^2.6.1",
66+
"mocha": "^10.8.2",
5667
"rimraf": "^6.1.2",
5768
"source-map-loader": "^5.0.0",
5869
"ts-loader": "^9.5.1",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import { strict as assert } from "node:assert";
7+
8+
import type { ContainerSchema } from "@fluidframework/fluid-static";
9+
import type { PropTreeNode } from "@fluidframework/react/alpha";
10+
import { treeDataObject, TreeViewComponent } from "@fluidframework/react/alpha";
11+
import { TinyliciousClient } from "@fluidframework/tinylicious-client";
12+
import { SchemaFactory, TreeViewConfiguration } from "@fluidframework/tree";
13+
// eslint-disable-next-line import-x/no-internal-modules
14+
import { independentView } from "@fluidframework/tree/internal";
15+
import { render } from "@testing-library/react";
16+
import globalJsdom from "global-jsdom";
17+
import * as React from "react";
18+
19+
import { Inventory, Part, treeConfiguration } from "../schema.js";
20+
import { MainView } from "../view/inventoryList.js";
21+
22+
describe("inventoryApp", () => {
23+
it("treeDataObject", async () => {
24+
const containerSchema = {
25+
initialObjects: {
26+
tree: treeDataObject(
27+
treeConfiguration,
28+
() =>
29+
new Inventory({
30+
parts: [
31+
new Part({ name: "nut", quantity: 5 }),
32+
new Part({ name: "bolt", quantity: 6 }),
33+
],
34+
}),
35+
),
36+
},
37+
} satisfies ContainerSchema;
38+
39+
const tinyliciousClient = new TinyliciousClient();
40+
41+
const { container } = await tinyliciousClient.createContainer(containerSchema, "2");
42+
const dataObject = container.initialObjects.tree;
43+
assert.equal(dataObject.treeView.root.parts.length, 2);
44+
const firstPart = dataObject.treeView.root.parts[0];
45+
assert(firstPart !== undefined);
46+
firstPart.quantity += 1;
47+
assert.equal(dataObject.treeView.root.parts[0]?.quantity, 6);
48+
assert.equal(dataObject.treeView.root.parts[1]?.quantity, 6);
49+
50+
container.dispose();
51+
});
52+
53+
describe("dom tests", () => {
54+
let cleanup: () => void;
55+
56+
before(() => {
57+
cleanup = globalJsdom();
58+
});
59+
60+
after(() => {
61+
cleanup();
62+
});
63+
64+
// Run without strict mode to make sure it works in a normal production setup.
65+
// Run with strict mode to potentially detect additional issues.
66+
for (const reactStrictMode of [false, true]) {
67+
describe(`StrictMode: ${reactStrictMode}`, () => {
68+
const builder = new SchemaFactory("tree-react-api");
69+
70+
class Item extends builder.object("Item", {}) {}
71+
72+
const View = ({ root }: { root: PropTreeNode<Item> }): React.JSX.Element => (
73+
<span>View</span>
74+
);
75+
76+
it("TreeViewComponent", () => {
77+
const view = independentView(new TreeViewConfiguration({ schema: Item }));
78+
const content = <TreeViewComponent viewComponent={View} tree={{ treeView: view }} />;
79+
const rendered = render(content, { reactStrictMode });
80+
81+
// Ensure that viewing an incompatible document displays an error.
82+
assert.match(rendered.baseElement.textContent ?? "", /Document is incompatible/);
83+
// Ensure that changes in compatibility are detected and invalidate the view,
84+
// and that compatible documents show the content from `viewComponent`
85+
view.initialize(new Item({}));
86+
rendered.rerender(content);
87+
assert.equal(rendered.baseElement.textContent, "View");
88+
});
89+
90+
it("renders MainView with inventory data", () => {
91+
const view = independentView(treeConfiguration);
92+
const content = (
93+
<TreeViewComponent viewComponent={MainView} tree={{ treeView: view }} />
94+
);
95+
const rendered = render(content, { reactStrictMode });
96+
97+
// Ensure that viewing an incompatible document displays an error.
98+
assert.match(rendered.baseElement.textContent ?? "", /Document is incompatible/);
99+
100+
// Initialize with inventory data
101+
view.initialize(
102+
new Inventory({
103+
parts: [
104+
new Part({ name: "bolt", quantity: 5 }),
105+
new Part({ name: "nut", quantity: 10 }),
106+
],
107+
}),
108+
);
109+
rendered.rerender(content);
110+
111+
// Verify the app renders the inventory header and parts
112+
assert.match(rendered.baseElement.textContent ?? "", /Inventory:/);
113+
assert.match(rendered.baseElement.textContent ?? "", /bolt/);
114+
assert.match(rendered.baseElement.textContent ?? "", /nut/);
115+
});
116+
});
117+
}
118+
});
119+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "../../../../../common/build/build-common/tsconfig.test.node16.json",
3+
"compilerOptions": {
4+
"rootDir": "./",
5+
"outDir": "../../lib/test",
6+
"types": ["mocha", "node"],
7+
},
8+
"include": ["./**/*"],
9+
"references": [
10+
{
11+
"path": "../..",
12+
},
13+
],
14+
}

examples/data-objects/inventory-app/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
"exactOptionalPropertyTypes": false,
77
},
88
"include": ["src/**/*"],
9+
"exclude": ["src/test/**"],
910
}

pnpm-lock.yaml

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)