Skip to content

Commit ed43044

Browse files
OrKoNDevtools-frontend LUCI CQ
authored andcommitted
Make SnapshotTester work in Node
Bug: 451502260 Change-Id: I55af7429084fa7a9e772b6231b3ccfcc14c23710 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7044330 Commit-Queue: Alex Rudenko <[email protected]> Reviewed-by: Simon Zünd <[email protected]>
1 parent 946045c commit ed43044

File tree

1 file changed

+97
-44
lines changed

1 file changed

+97
-44
lines changed

front_end/testing/SnapshotTester.ts

Lines changed: 97 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ function assertSnapshotContent(actual: string, expected: string): void {
2828
*
2929
* Note: karma.conf.ts implements the server logic (see snapshotTesterFactory).
3030
*/
31-
export class SnapshotTester {
31+
class BaseSnapshotTester {
3232
static #updateMode: boolean|null = null;
3333

34-
#snapshotPath: string;
34+
protected snapshotPath: string;
3535
#expected = new Map<string, string>();
3636
#actual = new Map<string, string>();
3737
#anyFailures = false;
@@ -41,27 +41,18 @@ export class SnapshotTester {
4141
// out/Default/gen/third_party/devtools-frontend/src/front_end/testing/SnapshotTester.test.js?8ee4f2b123e221040a4aa075a28d0e5b41d3d3ed
4242
// ->
4343
// front_end/testing/SnapshotTester.snapshot.txt
44-
this.#snapshotPath =
44+
this.snapshotPath =
4545
meta.url.substring(meta.url.lastIndexOf('front_end')).replace('.test.js', '.snapshot.txt').split('?')[0];
4646
}
4747

4848
async load() {
49-
if (SnapshotTester.#updateMode === null) {
50-
SnapshotTester.#updateMode = await this.#checkIfUpdateMode();
49+
if (BaseSnapshotTester.#updateMode === null) {
50+
BaseSnapshotTester.#updateMode = await this.checkIfUpdateMode();
5151
}
52-
53-
const url = new URL('/snapshot', import.meta.url);
54-
url.searchParams.set('snapshotPath', this.#snapshotPath);
55-
const response = await fetch(url);
56-
if (response.status === 404) {
57-
console.warn(`Snapshot file not found: ${this.#snapshotPath}. Will create it for you.`);
58-
return;
52+
const content = await this.loadSnapshot(this.snapshotPath);
53+
if (content) {
54+
this.#parseSnapshotFileContent(content);
5955
}
60-
if (response.status !== 200) {
61-
throw new Error('failed to load snapshot');
62-
}
63-
64-
this.#parseSnapshotFileContent(await response.text());
6556
}
6657

6758
assert(context: Mocha.Context, actual: string) {
@@ -81,7 +72,7 @@ export class SnapshotTester {
8172
const expected = this.#expected.get(title);
8273
if (expected === undefined) {
8374
this.#newTests = true;
84-
if (SnapshotTester.#updateMode) {
75+
if (BaseSnapshotTester.#updateMode) {
8576
return;
8677
}
8778

@@ -93,22 +84,12 @@ export class SnapshotTester {
9384
const isDifferent = actual !== expected;
9485
if (isDifferent) {
9586
this.#anyFailures = true;
96-
if (!SnapshotTester.#updateMode) {
87+
if (!BaseSnapshotTester.#updateMode) {
9788
assertSnapshotContent(actual, expected);
9889
}
9990
}
10091
}
10192

102-
async #postUpdate(): Promise<void> {
103-
const url = new URL('/update-snapshot', import.meta.url);
104-
url.searchParams.set('snapshotPath', this.#snapshotPath);
105-
const content = this.#serializeSnapshotFileContent();
106-
const response = await fetch(url, {method: 'POST', body: content});
107-
if (response.status !== 200) {
108-
throw new Error(`Unable to update snapshot ${url}`);
109-
}
110-
}
111-
11293
async finish() {
11394
let didAnyTestNotRun = false;
11495
for (const title of this.#expected.keys()) {
@@ -124,8 +105,8 @@ export class SnapshotTester {
124105
}
125106

126107
// If the update flag is on, post any and all changes (failures, new tests, removals).
127-
if (SnapshotTester.#updateMode) {
128-
await this.#postUpdate();
108+
if (BaseSnapshotTester.#updateMode) {
109+
await this.postUpdate();
129110
return;
130111
}
131112

@@ -137,7 +118,20 @@ export class SnapshotTester {
137118
}
138119
}
139120

140-
#serializeSnapshotFileContent(): string {
121+
#parseSnapshotFileContent(content: string): void {
122+
const sections = content.split('=== end content').map(s => s.trim()).filter(Boolean);
123+
for (const section of sections) {
124+
const [titleField, contentField, ...contentLines] = section.split('\n');
125+
const title = titleField.replace('Title:', '').trim();
126+
if (contentField !== 'Content:') {
127+
throw new Error('unexpected snapshot file');
128+
}
129+
const content = contentLines.join('\n').trim();
130+
this.#expected.set(title, content);
131+
}
132+
}
133+
134+
protected serializeSnapshotFileContent(): string {
141135
if (!this.#actual.size) {
142136
return '';
143137
}
@@ -153,22 +147,81 @@ export class SnapshotTester {
153147
return lines.join('\n').trim() + '\n';
154148
}
155149

156-
#parseSnapshotFileContent(content: string): void {
157-
const sections = content.split('=== end content').map(s => s.trim()).filter(Boolean);
158-
for (const section of sections) {
159-
const [titleField, contentField, ...contentLines] = section.split('\n');
160-
const title = titleField.replace('Title:', '').trim();
161-
if (contentField !== 'Content:') {
162-
throw new Error('unexpected snapshot file');
163-
}
164-
const content = contentLines.join('\n').trim();
165-
this.#expected.set(title, content);
166-
}
150+
protected async checkIfUpdateMode(): Promise<boolean> {
151+
return false;
152+
}
153+
154+
protected async postUpdate(): Promise<void> {
155+
throw new Error(`Not implemented`);
167156
}
168157

169-
async #checkIfUpdateMode(): Promise<boolean> {
158+
protected async loadSnapshot(_snapshotPath: string): Promise<string|undefined> {
159+
throw new Error('not implemented');
160+
}
161+
}
162+
163+
class WebSnapshotTester extends BaseSnapshotTester {
164+
protected override async checkIfUpdateMode(): Promise<boolean> {
170165
const response = await fetch('/snapshot-update-mode');
171166
const data = await response.json();
172167
return data.updateMode === true;
173168
}
169+
170+
protected override async postUpdate(): Promise<void> {
171+
const url = new URL('/update-snapshot', import.meta.url);
172+
url.searchParams.set('snapshotPath', this.snapshotPath);
173+
const content = this.serializeSnapshotFileContent();
174+
const response = await fetch(url, {method: 'POST', body: content});
175+
if (response.status !== 200) {
176+
throw new Error(`Unable to update snapshot ${url}`);
177+
}
178+
}
179+
180+
protected override async loadSnapshot(snapshotPath: string): Promise<string|undefined> {
181+
const url = new URL('/snapshot', import.meta.url);
182+
url.searchParams.set('snapshotPath', snapshotPath);
183+
const response = await fetch(url);
184+
if (response.status === 404) {
185+
console.warn(`Snapshot file not found: ${snapshotPath}. Will create it for you.`);
186+
return;
187+
}
188+
if (response.status !== 200) {
189+
throw new Error('failed to load snapshot');
190+
}
191+
return await response.text();
192+
}
193+
}
194+
195+
class NodeSnapshotTester extends BaseSnapshotTester {
196+
protected override async checkIfUpdateMode(): Promise<boolean> {
197+
// cannot update in node mode yet.
198+
return false;
199+
}
200+
201+
protected override async postUpdate(): Promise<void> {
202+
const content = this.serializeSnapshotFileContent();
203+
// @ts-expect-error no node types here.
204+
const fs = await import('node:fs/promises');
205+
await fs.writeFile(await this.#getSnapshotPath(this.snapshotPath), content);
206+
}
207+
208+
protected override async loadSnapshot(snapshotPath: string): Promise<string|undefined> {
209+
// @ts-expect-error no node types here.
210+
const fs = await import('node:fs/promises');
211+
return await fs.readFile(await this.#getSnapshotPath(snapshotPath), 'utf-8');
212+
}
213+
214+
async #getSnapshotPath(snapshotPath: string): Promise<string> {
215+
// @ts-expect-error no node types here.
216+
const path = await import('node:path');
217+
// @ts-expect-error no ESM types here.
218+
const SOURCE_ROOT = path.join(import.meta.dirname, '..', '..');
219+
return path.join(SOURCE_ROOT, snapshotPath);
220+
}
174221
}
222+
223+
export type SnapshotTester = NodeSnapshotTester|WebSnapshotTester;
224+
225+
const SnapshotTesterValue = (typeof window === 'undefined') ? NodeSnapshotTester : WebSnapshotTester;
226+
227+
export {SnapshotTesterValue as SnapshotTester};

0 commit comments

Comments
 (0)