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: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = ["./test/setup.ts"]
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "bun test"
},
"dependencies": {
"@aws-amplify/ui-react": "^6.5.5",
Expand Down
28 changes: 28 additions & 0 deletions test/amplify/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, test, expect } from "bun:test";
import { defineAuthCalls } from "../setup";
import { auth } from "../../amplify/auth/resource";

describe("amplify/auth/resource", () => {
test("auth export is defined", () => {
expect(auth).toBeDefined();
});

test("auth is the result of defineAuth", () => {
expect((auth as any)._type).toBe("auth");
});

test("defineAuth was called with loginWith.email set to true", () => {
const authCall = defineAuthCalls.find(
(args) => args[0]?.loginWith?.email === true
);
expect(authCall).toBeDefined();
expect(authCall![0].loginWith.email).toBe(true);
});

test("defineAuth was called exactly once for auth resource", () => {
const authCalls = defineAuthCalls.filter(
(args) => args[0]?.loginWith?.email === true
);
expect(authCalls.length).toBe(1);
});
});
20 changes: 20 additions & 0 deletions test/amplify/backend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, test, expect } from "bun:test";
import { defineBackendCalls } from "../setup";

// Import backend module to trigger defineBackend call
import "../../amplify/backend";

describe("amplify/backend", () => {
test("defineBackend was called", () => {
expect(defineBackendCalls.length).toBeGreaterThan(0);
});

test("defineBackend was called with auth and data properties", () => {
const backendCall = defineBackendCalls.find(
(args) => args[0]?.auth != null && args[0]?.data != null
);
expect(backendCall).toBeDefined();
expect(backendCall![0].auth).toBeDefined();
expect(backendCall![0].data).toBeDefined();
});
});
44 changes: 44 additions & 0 deletions test/amplify/data.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, test, expect } from "bun:test";
import { defineDataCalls } from "../setup";
import { data } from "../../amplify/data/resource";

describe("amplify/data/resource", () => {
test("data export is defined", () => {
expect(data).toBeDefined();
});

test("data is the result of defineData", () => {
expect((data as any)._type).toBe("data");
});

test("defineData was called with a config containing schema", () => {
const dataCall = defineDataCalls.find((args) => args[0]?.schema != null);
expect(dataCall).toBeDefined();
expect(dataCall![0].schema).toBeDefined();
});

test("defineData was called with authorizationModes", () => {
const dataCall = defineDataCalls.find(
(args) => args[0]?.authorizationModes != null
);
expect(dataCall).toBeDefined();
});

test("defaultAuthorizationMode is apiKey", () => {
const dataCall = defineDataCalls.find(
(args) => args[0]?.authorizationModes != null
);
expect(dataCall![0].authorizationModes.defaultAuthorizationMode).toBe(
"apiKey"
);
});

test("apiKeyAuthorizationMode.expiresInDays is 30", () => {
const dataCall = defineDataCalls.find(
(args) => args[0]?.authorizationModes != null
);
expect(
dataCall![0].authorizationModes.apiKeyAuthorizationMode.expiresInDays
).toBe(30);
});
});
38 changes: 38 additions & 0 deletions test/app/layout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, test, expect } from "bun:test";
import { metadata } from "../../app/layout";
import RootLayout from "../../app/layout";

describe("app/layout", () => {
test("metadata.title is 'Create Next App'", () => {
expect(metadata.title).toBe("Create Next App");
});

test("metadata.description is 'Generated by create next app'", () => {
expect(metadata.description).toBe("Generated by create next app");
});

test("RootLayout is a function", () => {
expect(typeof RootLayout).toBe("function");
});

test("RootLayout returns html element with lang='en'", () => {
const result = (RootLayout as any)({ children: "test" });
expect(result.type).toBe("html");
expect(result.props.lang).toBe("en");
});

test("body element has className from Inter mock ('mock-inter')", () => {
const result = (RootLayout as any)({ children: "test" });
// The html element's children should contain the body
const children = result.props.children;
// Find the body element - it could be a direct child or nested
let body: any;
if (Array.isArray(children)) {
body = children.find((c: any) => c?.type === "body");
} else if (children?.type === "body") {
body = children;
}
expect(body).toBeDefined();
expect(body.props.className).toBe("mock-inter");
});
});
97 changes: 97 additions & 0 deletions test/app/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { describe, test, expect } from "bun:test";
import App from "../../app/page";

/**
* Recursively search the JSX tree for a node matching the predicate.
* The mock JSX returns { type, props, key } objects where props.children
* contains nested elements (single child or array).
*/
function findInTree(
node: any,
predicate: (n: any) => boolean
): any | undefined {
if (node == null || typeof node !== "object") return undefined;
if (predicate(node)) return node;

const children = node.props?.children;
if (Array.isArray(children)) {
for (const child of children) {
const found = findInTree(child, predicate);
if (found) return found;
}
} else if (children != null && typeof children === "object") {
return findInTree(children, predicate);
}

return undefined;
}

/**
* Recursively collect all text content from the JSX tree.
*/
function collectText(node: any): string {
if (node == null) return "";
if (typeof node === "string") return node;
if (typeof node === "number") return String(node);
if (Array.isArray(node)) return node.map(collectText).join("");
if (typeof node === "object" && node.props) {
return collectText(node.props.children);
}
return "";
}

describe("app/page (App component)", () => {
const result = App();

test("App is a function", () => {
expect(typeof App).toBe("function");
});

test("App() returns a JSX structure with type and props", () => {
expect(result).toBeDefined();
expect(result.type).toBeDefined();
expect(result.props).toBeDefined();
});

test("root element type is 'main'", () => {
expect(result.type).toBe("main");
});

test("rendered output contains an h1 with 'My todos'", () => {
const h1 = findInTree(
result,
(n) => n.type === "h1"
);
expect(h1).toBeDefined();
const text = collectText(h1);
expect(text).toContain("My todos");
});

test("rendered output contains a button with '+ new' text", () => {
const button = findInTree(
result,
(n) => n.type === "button"
);
expect(button).toBeDefined();
const text = collectText(button);
expect(text).toContain("+ new");
});

test("rendered output contains a ul element", () => {
const ul = findInTree(result, (n) => n.type === "ul");
expect(ul).toBeDefined();
});

test("rendered output contains a link to Amplify docs", () => {
const link = findInTree(
result,
(n) =>
n.type === "a" &&
n.props?.href?.includes("docs.amplify.aws")
);
expect(link).toBeDefined();
expect(link.props.href).toBe(
"https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/"
);
});
});
14 changes: 14 additions & 0 deletions test/next-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, test, expect } from "bun:test";

const nextConfig = require("../next.config.js");

describe("next.config.js", () => {
test("exports an object", () => {
expect(typeof nextConfig).toBe("object");
expect(nextConfig).not.toBeNull();
});

test("exported object is empty (no custom config keys)", () => {
expect(Object.keys(nextConfig).length).toBe(0);
});
});
Loading