Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration tests admin UI #6260

Merged
merged 17 commits into from
Sep 1, 2021
Merged
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: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ jobs:
DATABASE_URL: 'file:./test.db'
strategy:
matrix:
test: ['init.test.ts', 'list-view-crud.test.ts']
test: ['init.test.ts', 'list-view-crud.test.ts', 'navigation.test.ts']
fail-fast: false
steps:
- name: Checkout Repo
Expand Down
10 changes: 10 additions & 0 deletions tests/admin-ui-tests/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ adminUITests('./tests/test-projects/basic', browserType => {
test('Task List card should be visible', async () => {
await page.waitForSelector('h3:has-text("Task")');
});
test('Clicking on the logo should return you to the Dashboard route', async () => {
await page.goto('http://localhost:3000/tasks');
await page.waitForSelector('h3 a:has-text("Keystone 6")');
await Promise.all([
page.waitForNavigation({
url: 'http://localhost:3000',
}),
page.click('h3 a:has-text("Keystone 6")'),
]);
});
test('Should see a 404 on request of the /init route', async () => {
await page.goto('http://localhost:3000/init');
const content = await page.textContent('body h1');
Expand Down
30 changes: 5 additions & 25 deletions tests/admin-ui-tests/list-view-crud.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,10 @@
import { Browser, Page } from 'playwright';
import fetch from 'node-fetch';
import { adminUITests, deleteAllData } from './utils';

import { makeGqlRequest, adminUITests, deleteAllData } from './utils';

adminUITests('./tests/test-projects/crud-notifications', browserType => {
let browser: Browser = undefined as any;
let page: Page = undefined as any;
const seedData = async (query: string, variables?: Record<string, any>) => {
try {
const { errors } = await fetch('http://localhost:3000/api/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
}).then(res => res.json());
if (errors) {
throw errors;
}
} catch (e) {
console.log(e);
throw e;
}
};

beforeAll(async () => {
browser = await browserType.launch();
Expand All @@ -46,7 +26,7 @@ adminUITests('./tests/test-projects/crud-notifications', browserType => {
}
}
`;
await seedData(query);
await makeGqlRequest(query);
await Promise.all([page.waitForNavigation(), page.goto('http://localhost:3000/tasks')]);
await page.waitForSelector('tbody tr:first-of-type td:first-of-type label');
await page.click('tbody tr:first-of-type td:first-of-type label');
Expand All @@ -71,7 +51,7 @@ adminUITests('./tests/test-projects/crud-notifications', browserType => {
}
}
`;
await seedData(query);
await makeGqlRequest(query);
await Promise.all([page.waitForNavigation(), page.goto('http://localhost:3000/tasks')]);
await page.click('tbody tr:first-of-type td:first-of-type label');
await page.click('button:has-text("Delete")');
Expand Down Expand Up @@ -105,7 +85,7 @@ adminUITests('./tests/test-projects/crud-notifications', browserType => {
}
}),
};
await seedData(query, variables);
await makeGqlRequest(query, variables);
await Promise.all([
page.waitForNavigation(),
page.goto('http://localhost:3000/tasks?sortBy=label&page=1'),
Expand Down
75 changes: 75 additions & 0 deletions tests/admin-ui-tests/navigation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Browser, Page } from 'playwright';
import { adminUITests } from './utils';

adminUITests('./tests/test-projects/basic', browserType => {
let browser: Browser = undefined as any;
let page: Page = undefined as any;

beforeAll(async () => {
browser = await browserType.launch();
page = await browser.newPage();
await page.goto('http://localhost:3000');
});
test('Nav contains a Dashboard route by default', async () => {
await page.waitForSelector('nav a:has-text("Dashboard")');
});
test('When at the index, the Dashboard NavItem is selected', async () => {
const element = await page.waitForSelector('nav a:has-text("Dashboard")');
const ariaCurrent = await element?.getAttribute('aria-current');
expect(ariaCurrent).toBe('location');
});
test('When navigated to a List route, the representative list NavItem is selected', async () => {
await page.goto('http://localhost:3000/tasks');
const element = await page.waitForSelector('nav a:has-text("Tasks")');
const ariaCurrent = await element?.getAttribute('aria-current');
expect(ariaCurrent).toBe('location');
});
test('Can access all list pages via the navigation', async () => {
await page.goto('http://localhost:3000');
await Promise.all([
page.waitForNavigation({
url: 'http://localhost:3000/tasks',
}),
page.click('nav a:has-text("Tasks")'),
]);
await Promise.all([
page.waitForNavigation({
url: 'http://localhost:3000/people',
}),
page.click('nav a:has-text("People")'),
]);
});
test('Can not access hidden lists via the navigation', async () => {
await Promise.all([page.waitForNavigation(), page.goto('http://localhost:3000')]);
await page.waitForSelector('nav');
const navItems = await page.$$('nav li a');
const navLinks = await Promise.all(
navItems.map(navItem => {
return navItem.getAttribute('href');
})
);
expect(navLinks.length).toBe(3);
expect(navLinks.includes('/secretplans')).toBe(false);
});
test('When navigated to an Item view, the representative list NavItem is selected', async () => {
await page.goto('http://localhost:3000');
await page.click('button[title="Create Task"]');
await page.fill('id=label', 'Test Task');
await Promise.all([page.waitForNavigation(), page.click('button[type="submit"]')]);
const element = await page.waitForSelector('nav a:has-text("Tasks")');
const ariaCurrent = await element?.getAttribute('aria-current');
expect(ariaCurrent).toBe('location');
});
test('When pressing a list view nav item from an item view, the correct route should be reached', async () => {
await page.goto('http://localhost:3000');
await page.click('button[title="Create Task"]');
await page.fill('id=label', 'Test Task');
await Promise.all([page.waitForNavigation(), page.click('button[type="submit"]')]);
await Promise.all([page.waitForNavigation(), page.click('nav a:has-text("Tasks")')]);

expect(page.url()).toBe('http://localhost:3000/tasks');
});
afterAll(async () => {
await browser.close();
});
});
18 changes: 18 additions & 0 deletions tests/admin-ui-tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path';
import fs from 'fs';
import { promisify } from 'util';
import fetch from 'node-fetch';
import execa from 'execa';
import _treeKill from 'tree-kill';
import * as playwright from 'playwright';
Expand All @@ -21,6 +22,23 @@ const promiseSignal = (): Promise<void> & { resolve: () => void } => {
};
const projectRoot = findRootSync(process.cwd());

export const makeGqlRequest = async (query: string, variables?: Record<string, any>) => {
const { errors } = await fetch('http://localhost:3000/api/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
}).then(res => res.json());

if (errors) {
throw new Error(`graphql errors: ${errors.map((x: Error) => x.message).join('\n')}`);
}
};

export const deleteAllData: (projectDir: string) => Promise<void> = async (projectDir: string) => {
const { PrismaClient } = require(path.resolve(
projectRoot,
Expand Down
53 changes: 53 additions & 0 deletions tests/test-projects/basic/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,42 @@ input TaskRelateToManyForCreateInput {
connect: [TaskWhereUniqueInput!]
}

type SecretPlan {
id: ID!
label: String
description: String
}

input SecretPlanWhereUniqueInput {
id: ID
}

input SecretPlanWhereInput {
AND: [SecretPlanWhereInput!]
OR: [SecretPlanWhereInput!]
NOT: [SecretPlanWhereInput!]
id: IDFilter
}

input SecretPlanOrderByInput {
id: OrderDirection
}

input SecretPlanUpdateInput {
label: String
description: String
}

input SecretPlanUpdateArgs {
where: SecretPlanWhereUniqueInput!
data: SecretPlanUpdateInput!
}

input SecretPlanCreateInput {
label: String
description: String
}

"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
Expand All @@ -223,6 +259,15 @@ type Mutation {
updatePeople(data: [PersonUpdateArgs!]!): [Person]
deletePerson(where: PersonWhereUniqueInput!): Person
deletePeople(where: [PersonWhereUniqueInput!]!): [Person]
createSecretPlan(data: SecretPlanCreateInput!): SecretPlan
createSecretPlans(data: [SecretPlanCreateInput!]!): [SecretPlan]
updateSecretPlan(
where: SecretPlanWhereUniqueInput!
data: SecretPlanUpdateInput!
): SecretPlan
updateSecretPlans(data: [SecretPlanUpdateArgs!]!): [SecretPlan]
deleteSecretPlan(where: SecretPlanWhereUniqueInput!): SecretPlan
deleteSecretPlans(where: [SecretPlanWhereUniqueInput!]!): [SecretPlan]
}

type Query {
Expand All @@ -242,6 +287,14 @@ type Query {
): [Person!]
person(where: PersonWhereUniqueInput!): Person
peopleCount(where: PersonWhereInput! = {}): Int
secretPlans(
where: SecretPlanWhereInput! = {}
orderBy: [SecretPlanOrderByInput!]! = []
take: Int
skip: Int! = 0
): [SecretPlan!]
secretPlan(where: SecretPlanWhereUniqueInput!): SecretPlan
secretPlansCount(where: SecretPlanWhereInput! = {}): Int
keystone: KeystoneMeta!
}

Expand Down
6 changes: 6 additions & 0 deletions tests/test-projects/basic/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ model Person {
id String @id @default(cuid())
name String?
tasks Task[] @relation("Task_assignedTo")
}

model SecretPlan {
id String @id @default(cuid())
label String?
description String?
}
9 changes: 9 additions & 0 deletions tests/test-projects/basic/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,13 @@ export const lists = createSchema({
defaultIsFilterable: true,
defaultIsOrderable: true,
}),
SecretPlan: list({
fields: {
label: text(),
description: text(),
},
ui: {
isHidden: true,
},
}),
});