-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
live-reloading.test.ts
158 lines (136 loc) · 5.15 KB
/
live-reloading.test.ts
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import path from 'path';
import fs from 'fs/promises';
import { Browser, Page, chromium } from 'playwright';
import { parse, print } from 'graphql';
import { ExecaChildProcess } from 'execa';
import { generalStartKeystone, loadIndex, makeGqlRequest, promiseSignal } from './utils';
const gql = ([content]: TemplateStringsArray) => content;
const testProjectPath = path.join(__dirname, '..', 'test-projects', 'live-reloading');
async function replaceSchema(
schema: 'changed-prisma-schema' | 'initial' | 'runtime-error' | 'second' | 'syntax-error'
) {
await fs.writeFile(
path.join(testProjectPath, 'schemas/index.ts'),
`export * from './${schema}';\n`
);
}
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
let exit = async () => {};
let process: ExecaChildProcess = undefined as any;
let page: Page = undefined as any;
let browser: Browser = undefined as any;
test('start keystone', async () => {
// just in case a previous failing test run messed things up, let's reset it
await replaceSchema('initial');
({ exit, process } = await generalStartKeystone(testProjectPath, 'dev'));
browser = await chromium.launch();
page = await browser.newPage();
await loadIndex(page);
});
test('Creating an item with the GraphQL API and navigating to the item page for it', async () => {
const {
createSomething: { id },
} = await makeGqlRequest(gql`
mutation {
createSomething(data: { text: "blah" }) {
id
}
}
`);
await page.goto(`http://localhost:3000/somethings/${id}`);
await page.waitForSelector('label:has-text("Text")');
const element = await page.waitForSelector(
'label:has-text("Initial Label For Text") >> .. >> input'
);
const value = await element.inputValue();
expect(value).toBe('blah');
});
test('changing the label of a field updates in the Admin UI', async () => {
await replaceSchema('second');
const element = await page.waitForSelector(
'label:has-text("Very Important Text") >> .. >> input'
);
const value = await element.inputValue();
expect(value).toBe('blah');
});
test('adding a virtual field', async () => {
const element = await page.waitForSelector('label:has-text("Virtual") >> ..');
const value = await element.textContent();
expect(value).toBe('Virtualblah');
});
test('the generated schema includes schema updates', async () => {
// we want to make sure the field that we added worked
// and the change we made to the have worked
const schema = await fs.readFile(path.join(testProjectPath, 'schema.graphql'), 'utf8');
const parsed = parse(schema);
const objectTypes = parsed.definitions.filter(
x =>
x.kind === 'ObjectTypeDefinition' &&
(x.name.value === 'Query' || x.name.value === 'Something')
);
expect(objectTypes.map(x => print(x)).join('\n\n')).toMatchInlineSnapshot(`
"type Query {
someNumber: Int!
somethings(where: SomethingWhereInput! = {}, orderBy: [SomethingOrderByInput!]! = [], take: Int, skip: Int! = 0): [Something!]
something(where: SomethingWhereUniqueInput!): Something
somethingsCount(where: SomethingWhereInput! = {}): Int
keystone: KeystoneMeta!
}
type Something {
id: ID!
text: String
virtual: String
}"
`);
});
test("a syntax error is shown and doesn't crash the process", async () => {
await replaceSchema('syntax-error');
await expectContentInStdio(process, 'error - ../../schemas/syntax-error.js');
});
test("a runtime error is shown and doesn't crash the process", async () => {
await replaceSchema('runtime-error');
await expectContentInStdio(process, 'ReferenceError: doesNotExist is not defined');
});
test('errors can be recovered from', async () => {
await replaceSchema('initial');
const element = await page.waitForSelector(
'label:has-text("Initial Label For Text") >> .. >> input'
);
const value = await element.inputValue();
expect(value).toBe('blah');
});
test('changing the prisma schema crashes the process', async () => {
await replaceSchema('changed-prisma-schema');
await expectContentInStdio(process, 'Your prisma schema has changed, please restart Keystone');
// the promise will reject when it exits with a non-zero exit code which is what we're expecting here
await process.catch(() => {});
expect(process.exitCode).toBe(1);
});
afterAll(async () => {
await Promise.all([replaceSchema('initial'), exit(), browser.close()]);
});
async function expectContentInStdio(process: ExecaChildProcess, content: string) {
let promise = promiseSignal();
const listener = (chunk: any) => {
const stringified: string = chunk.toString('utf8');
if (stringified.includes(content)) {
console.log('found content');
promise.resolve();
} else {
console.log('did not find content, found:', stringified);
}
};
process.stdout!.on('data', listener);
process.stderr!.on('data', listener);
try {
await Promise.race([
promise,
wait(10000).then(() => {
throw new Error(`timed out when waiting for ${content}`);
}),
]);
} finally {
process.stdout!.off('data', listener);
process.stderr!.off('data', listener);
}
}