~james/core

core/web/scripts/testids.test.ts -rw-r--r-- 5.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { describe, expect, it } from "vitest";
import { apply, transform } from "./testids.ts";

const run = (source: string, options?: Parameters<typeof transform>[1]) => {
	const { inserts, seen } = transform(source, options);
	return { out: apply(source, inserts), seen };
};

const idOf = (markup: string) => run(markup).seen[0]?.id ?? null;

describe("handlers", () => {
	it("names a plain handler", () =>
		expect(idOf("<button onclick={deleteIssue}>x</button>")).toBe("deleteIssue"));
	it("names the call inside an arrow", () => {
		expect(idOf("<button onclick={() => archive(issue)}>x</button>")).toBe("archive");
		expect(idOf("<button onclick={() => void copy()}>x</button>")).toBe("copy");
		expect(idOf("<button onclick={() => file && upload(file)}>x</button>")).toBe("upload");
	});
	it("names an action() call after the action, not .run", () =>
		expect(idOf("<button onclick={() => save.run(draft)}>x</button>")).toBe("save"));
	it("reads assignments as what they do", () => {
		expect(idOf("<button onclick={() => (open = false)}>x</button>")).toBe("close");
		expect(idOf("<button onclick={() => (drawerOpen = true)}>x</button>")).toBe("openDrawer");
		expect(idOf("<button onclick={() => (menu = !menu)}>x</button>")).toBe("toggleMenu");
	});
	it("drops the on prefix a prop carries", () => {
		expect(idOf("<button onclick={onCancel}>x</button>")).toBe("cancel");
		expect(idOf("<button onclick={onconfirm}>x</button>")).toBe("confirm");
	});
	it("leaves a forwarded onclick for the call site", () =>
		expect(idOf("<button {type} onclick={onclick}>x</button>")).toBeNull());
	it("gives up on a handler with no single action", () =>
		expect(idOf("<button onclick={() => { a(); b(); }}>x</button>")).toBeNull());
});

describe("links", () => {
	it("names a literal href by its last word", () => {
		expect(idOf('<a href="/settings/keys">x</a>')).toBe("keys");
		expect(idOf('<Link href={resolve("/signup")}>x</Link>')).toBe("signup");
	});
	it("skips the parts only known at runtime", () => {
		expect(idOf("<a href={`/${handle}/${repo}/issues`}>x</a>")).toBe("issues");
		expect(idOf("<a href={`/${handle}`}>x</a>")).toBeNull();
	});
	it("names a variable href after the variable", () =>
		expect(idOf("<a href={newRepoHref}>x</a>")).toBe("newRepoHref"));
	it("ignores external links and a forwarded href", () => {
		expect(idOf('<a href="https://example.com">x</a>')).toBeNull();
		expect(idOf("<a {href}>x</a>")).toBeNull();
	});
});

describe("fields", () => {
	it("names an input after what it binds", () =>
		expect(idOf("<Input bind:value={form.title} />")).toBe("formTitle"));
	it("names a submit button submit", () =>
		expect(idOf('<button type="submit">x</button>')).toBe("submit"));
	it("prefers a handler over a name", () =>
		expect(idOf('<button name="a" onclick={go}>x</button>')).toBe("go"));
});

describe("injection", () => {
	it("puts the id on a component call site", () =>
		expect(run("<Button onclick={save}>x</Button>").out).toBe(
			'<Button data-testid="save" onclick={save}>x</Button>'
		));
	it("leaves a hand-written id alone", () => {
		const { out, seen } = run('<button data-testid="mine" onclick={save}>x</button>');
		expect(out).toBe('<button data-testid="mine" onclick={save}>x</button>');
		expect(seen.map((item) => [item.id, item.source])).toEqual([["mine", "hand"]]);
	});
	it("does not touch elements that are not interactive", () =>
		expect(run("<div><p>hi</p></div>").out).toBe("<div><p>hi</p></div>"));
	it("does not touch a component it has no rule for", () =>
		expect(run("<Avatar bind:value={x}>y</Avatar>").out).toBe(
			"<Avatar bind:value={x}>y</Avatar>"
		));
	it("marks the root elements with the component, through blocks too", () => {
		const { out } = run("{#if a}<section>x</section>{/if}<aside>y</aside>", {
			component: "Panel"
		});
		expect(out).toBe(
			'{#if a}<section data-component="Panel">x</section>{/if}<aside data-component="Panel">y</aside>'
		);
	});
	it("does not mark elements nested in another", () =>
		expect(run("<div><span>x</span></div>", { component: "Panel" }).out).toBe(
			'<div data-component="Panel"><span>x</span></div>'
		));
	it("copes with typescript in the script", () =>
		expect(
			run(
				'<script lang="ts">let n: number = $state(0);</script><button onclick={() => n++}>x</button>'
			).out
		).toContain("<button onclick"));
});

describe("instanced components", () => {
	it("names a modal after what it binds, without the Open", () => {
		expect(idOf("<ConfirmModal bind:open={deleteConfirmOpen} title={t} />")).toBe(
			"deleteConfirm"
		);
		expect(run("<Modal bind:open={showKeys}>x</Modal>").out).toContain('testid="showKeys"');
	});
	it("does not name a modal after a bare open", () =>
		expect(idOf("<Modal bind:open>x</Modal><Modal bind:open>y</Modal>")).toBeNull());
	it("names a menu after its id literal", () =>
		expect(idOf('<Dropdown id="profile-menu" group="topbar">x</Dropdown>')).toBe(
			"profileMenu"
		));
	it("falls back to the tag only when it is alone in the file", () => {
		expect(run("<MoreMenu>x</MoreMenu>").out).toBe('<MoreMenu testid="moreMenu">x</MoreMenu>');
		expect(
			run("<MoreMenu>x</MoreMenu><MoreMenu>y</MoreMenu>").seen.map((item) => item.id)
		).toEqual([null, null]);
	});
	it("leaves a hand-written testid prop alone", () =>
		expect(run('<Modal testid="mine" bind:open={a}>x</Modal>').out).toBe(
			'<Modal testid="mine" bind:open={a}>x</Modal>'
		));
	it("lists a hand-written id on any element, not only the interactive ones", () =>
		expect(run('<h1 data-testid="title">x</h1>').seen.map((item) => item.id)).toEqual([
			"title"
		]));
	it("passes data-component through a root Card, which spreads rest onto its own root", () =>
		expect(run('<Card padding="none">x</Card>', { component: "Panel" }).out).toBe(
			'<Card data-component="Panel" padding="none">x</Card>'
		));
	it("does not mark a root component that cannot take it", () =>
		expect(run("<ProfileTabs />", { component: "Panel" }).out).toBe("<ProfileTabs />"));
});