chore: setup coupling graph generation

This commit is contained in:
Bart Stefanski 2023-04-21 12:57:01 +02:00
parent ad7b3d3d16
commit 9e3239d742
3 changed files with 87 additions and 438 deletions

85
graph.svg Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 8.0.2 (20230410.1723)
-->
<!-- Title: G Pages: 1 -->
<svg width="677pt" height="230pt"
viewBox="0.00 0.00 677.20 230.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(21.6 208.6)">
<title>G</title>
<polygon fill="#111111" stroke="none" points="-21.6,21.6 -21.6,-208.6 655.6,-208.6 655.6,21.6 -21.6,21.6"/>
<!-- components/Button/Button.stories.tsx -->
<g id="node1" class="node">
<title>components/Button/Button.stories.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M233.33,-64C233.33,-64 7.67,-64 7.67,-64 3.83,-64 0,-60.17 0,-56.33 0,-56.33 0,-48.67 0,-48.67 0,-44.83 3.83,-41 7.67,-41 7.67,-41 233.33,-41 233.33,-41 237.17,-41 241,-44.83 241,-48.67 241,-48.67 241,-56.33 241,-56.33 241,-60.17 237.17,-64 233.33,-64"/>
<text text-anchor="middle" x="120.5" y="-48.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">components/Button/Button.stories.tsx</text>
</g>
<!-- components/Button/Button.tsx -->
<g id="node2" class="node">
<title>components/Button/Button.tsx</title>
<path fill="none" stroke="#cfffac" d="M466.33,-43C466.33,-43 284.67,-43 284.67,-43 280.83,-43 277,-39.17 277,-35.33 277,-35.33 277,-27.67 277,-27.67 277,-23.83 280.83,-20 284.67,-20 284.67,-20 466.33,-20 466.33,-20 470.17,-20 474,-23.83 474,-27.67 474,-27.67 474,-35.33 474,-35.33 474,-39.17 470.17,-43 466.33,-43"/>
<text text-anchor="middle" x="375.5" y="-27.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Button/Button.tsx</text>
</g>
<!-- components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge1" class="edge">
<title>components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M241.33,-42.55C249.59,-41.87 257.89,-41.18 266.08,-40.5"/>
<polygon fill="#757575" stroke="#757575" points="266.1,-43.93 275.78,-39.61 265.53,-36.95 266.1,-43.93"/>
</g>
<!-- components/Tooltip/Tooltip.tsx -->
<g id="node3" class="node">
<title>components/Tooltip/Tooltip.tsx</title>
<path fill="none" stroke="#cfffac" d="M211.33,-105C211.33,-105 29.67,-105 29.67,-105 25.83,-105 22,-101.17 22,-97.33 22,-97.33 22,-89.67 22,-89.67 22,-85.83 25.83,-82 29.67,-82 29.67,-82 211.33,-82 211.33,-82 215.17,-82 219,-85.83 219,-89.67 219,-89.67 219,-97.33 219,-97.33 219,-101.17 215.17,-105 211.33,-105"/>
<text text-anchor="middle" x="120.5" y="-89.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Tooltip/Tooltip.tsx</text>
</g>
<!-- pages/_app.test.tsx -->
<g id="node4" class="node">
<title>pages/_app.test.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M179.83,-146C179.83,-146 61.17,-146 61.17,-146 57.33,-146 53.5,-142.17 53.5,-138.33 53.5,-138.33 53.5,-130.67 53.5,-130.67 53.5,-126.83 57.33,-123 61.17,-123 61.17,-123 179.83,-123 179.83,-123 183.67,-123 187.5,-126.83 187.5,-130.67 187.5,-130.67 187.5,-138.33 187.5,-138.33 187.5,-142.17 183.67,-146 179.83,-146"/>
<text text-anchor="middle" x="120.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.test.tsx</text>
</g>
<!-- pages/_app.tsx -->
<g id="node5" class="node">
<title>pages/_app.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M421.83,-146C421.83,-146 329.17,-146 329.17,-146 325.33,-146 321.5,-142.17 321.5,-138.33 321.5,-138.33 321.5,-130.67 321.5,-130.67 321.5,-126.83 325.33,-123 329.17,-123 329.17,-123 421.83,-123 421.83,-123 425.67,-123 429.5,-126.83 429.5,-130.67 429.5,-130.67 429.5,-138.33 429.5,-138.33 429.5,-142.17 425.67,-146 421.83,-146"/>
<text text-anchor="middle" x="375.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.tsx</text>
</g>
<!-- pages/_app.test.tsx&#45;&gt;pages/_app.tsx -->
<g id="edge2" class="edge">
<title>pages/_app.test.tsx&#45;&gt;pages/_app.tsx</title>
<path fill="none" stroke="#757575" d="M187.97,-134.5C225.82,-134.5 272.99,-134.5 310.43,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="310.21,-138 320.21,-134.5 310.21,-131 310.21,-138"/>
</g>
<!-- styles/tailwind.css -->
<g id="node6" class="node">
<title>styles/tailwind.css</title>
<path fill="none" stroke="#cfffac" d="M626.33,-146C626.33,-146 517.67,-146 517.67,-146 513.83,-146 510,-142.17 510,-138.33 510,-138.33 510,-130.67 510,-130.67 510,-126.83 513.83,-123 517.67,-123 517.67,-123 626.33,-123 626.33,-123 630.17,-123 634,-126.83 634,-130.67 634,-130.67 634,-138.33 634,-138.33 634,-142.17 630.17,-146 626.33,-146"/>
<text text-anchor="middle" x="572" y="-130.8" font-family="Arial" font-size="14.00" fill="#cfffac">styles/tailwind.css</text>
</g>
<!-- pages/_app.tsx&#45;&gt;styles/tailwind.css -->
<g id="edge3" class="edge">
<title>pages/_app.tsx&#45;&gt;styles/tailwind.css</title>
<path fill="none" stroke="#757575" d="M429.91,-134.5C451.26,-134.5 476.19,-134.5 499.1,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="498.81,-138 508.81,-134.5 498.81,-131 498.81,-138"/>
</g>
<!-- pages/api/health.ts -->
<g id="node7" class="node">
<title>pages/api/health.ts</title>
<path fill="none" stroke="#cfffac" d="M177.83,-187C177.83,-187 63.17,-187 63.17,-187 59.33,-187 55.5,-183.17 55.5,-179.33 55.5,-179.33 55.5,-171.67 55.5,-171.67 55.5,-167.83 59.33,-164 63.17,-164 63.17,-164 177.83,-164 177.83,-164 181.67,-164 185.5,-167.83 185.5,-171.67 185.5,-171.67 185.5,-179.33 185.5,-179.33 185.5,-183.17 181.67,-187 177.83,-187"/>
<text text-anchor="middle" x="120.5" y="-171.8" font-family="Arial" font-size="14.00" fill="#cfffac">pages/api/health.ts</text>
</g>
<!-- pages/index.tsx -->
<g id="node8" class="node">
<title>pages/index.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M167.83,-23C167.83,-23 73.17,-23 73.17,-23 69.33,-23 65.5,-19.17 65.5,-15.33 65.5,-15.33 65.5,-7.67 65.5,-7.67 65.5,-3.83 69.33,0 73.17,0 73.17,0 167.83,0 167.83,0 171.67,0 175.5,-3.83 175.5,-7.67 175.5,-7.67 175.5,-15.33 175.5,-15.33 175.5,-19.17 171.67,-23 167.83,-23"/>
<text text-anchor="middle" x="120.5" y="-7.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/index.tsx</text>
</g>
<!-- pages/index.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge4" class="edge">
<title>pages/index.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M175.77,-15.79C202.08,-17.87 234.64,-20.44 265.75,-22.9"/>
<polygon fill="#757575" stroke="#757575" points="265.43,-26.47 275.67,-23.77 265.98,-19.49 265.43,-26.47"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -18,7 +18,8 @@
"e2e:ui": "playwright test --ui",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"postinstall": "npx patch-package -y",
"preinstall": "npx only-allow yarn --yes"
"preinstall": "npx only-allow yarn --yes",
"coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.1.1",

View file

@ -1,437 +0,0 @@
import { test, expect, type Page } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc');
});
const TODO_ITEMS = [
'buy some cheese',
'feed the cat',
'book a doctors appointment'
];
test.describe('New Todo', () => {
test('should allow me to add todo items', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// Create 1st todo.
await newTodo.fill(TODO_ITEMS[0]);
await newTodo.press('Enter');
// Make sure the list only has one todo item.
await expect(page.getByTestId('todo-title')).toHaveText([
TODO_ITEMS[0]
]);
// Create 2nd todo.
await newTodo.fill(TODO_ITEMS[1]);
await newTodo.press('Enter');
// Make sure the list now has two todo items.
await expect(page.getByTestId('todo-title')).toHaveText([
TODO_ITEMS[0],
TODO_ITEMS[1]
]);
await checkNumberOfTodosInLocalStorage(page, 2);
});
test('should clear text input field when an item is added', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// Create one todo item.
await newTodo.fill(TODO_ITEMS[0]);
await newTodo.press('Enter');
// Check that input is empty.
await expect(newTodo).toBeEmpty();
await checkNumberOfTodosInLocalStorage(page, 1);
});
test('should append new items to the bottom of the list', async ({ page }) => {
// Create 3 items.
await createDefaultTodos(page);
// create a todo count locator
const todoCount = page.getByTestId('todo-count')
// Check test using different methods.
await expect(page.getByText('3 items left')).toBeVisible();
await expect(todoCount).toHaveText('3 items left');
await expect(todoCount).toContainText('3');
await expect(todoCount).toHaveText(/3/);
// Check all items in one call.
await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS);
await checkNumberOfTodosInLocalStorage(page, 3);
});
});
test.describe('Mark all as completed', () => {
test.beforeEach(async ({ page }) => {
await createDefaultTodos(page);
await checkNumberOfTodosInLocalStorage(page, 3);
});
test.afterEach(async ({ page }) => {
await checkNumberOfTodosInLocalStorage(page, 3);
});
test('should allow me to mark all items as completed', async ({ page }) => {
// Complete all todos.
await page.getByLabel('Mark all as complete').check();
// Ensure all todos have 'completed' class.
await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']);
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
});
test('should allow me to clear the complete state of all items', async ({ page }) => {
const toggleAll = page.getByLabel('Mark all as complete');
// Check and then immediately uncheck.
await toggleAll.check();
await toggleAll.uncheck();
// Should be no completed classes.
await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']);
});
test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => {
const toggleAll = page.getByLabel('Mark all as complete');
await toggleAll.check();
await expect(toggleAll).toBeChecked();
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
// Uncheck first todo.
const firstTodo = page.getByTestId('todo-item').nth(0);
await firstTodo.getByRole('checkbox').uncheck();
// Reuse toggleAll locator and make sure its not checked.
await expect(toggleAll).not.toBeChecked();
await firstTodo.getByRole('checkbox').check();
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
// Assert the toggle all is checked again.
await expect(toggleAll).toBeChecked();
});
});
test.describe('Item', () => {
test('should allow me to mark items as complete', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// Create two items.
for (const item of TODO_ITEMS.slice(0, 2)) {
await newTodo.fill(item);
await newTodo.press('Enter');
}
// Check first item.
const firstTodo = page.getByTestId('todo-item').nth(0);
await firstTodo.getByRole('checkbox').check();
await expect(firstTodo).toHaveClass('completed');
// Check second item.
const secondTodo = page.getByTestId('todo-item').nth(1);
await expect(secondTodo).not.toHaveClass('completed');
await secondTodo.getByRole('checkbox').check();
// Assert completed class.
await expect(firstTodo).toHaveClass('completed');
await expect(secondTodo).toHaveClass('completed');
});
test('should allow me to un-mark items as complete', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// Create two items.
for (const item of TODO_ITEMS.slice(0, 2)) {
await newTodo.fill(item);
await newTodo.press('Enter');
}
const firstTodo = page.getByTestId('todo-item').nth(0);
const secondTodo = page.getByTestId('todo-item').nth(1);
const firstTodoCheckbox = firstTodo.getByRole('checkbox');
await firstTodoCheckbox.check();
await expect(firstTodo).toHaveClass('completed');
await expect(secondTodo).not.toHaveClass('completed');
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
await firstTodoCheckbox.uncheck();
await expect(firstTodo).not.toHaveClass('completed');
await expect(secondTodo).not.toHaveClass('completed');
await checkNumberOfCompletedTodosInLocalStorage(page, 0);
});
test('should allow me to edit an item', async ({ page }) => {
await createDefaultTodos(page);
const todoItems = page.getByTestId('todo-item');
const secondTodo = todoItems.nth(1);
await secondTodo.dblclick();
await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]);
await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter');
// Explicitly assert the new text value.
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2]
]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
});
test.describe('Editing', () => {
test.beforeEach(async ({ page }) => {
await createDefaultTodos(page);
await checkNumberOfTodosInLocalStorage(page, 3);
});
test('should hide other controls when editing', async ({ page }) => {
const todoItem = page.getByTestId('todo-item').nth(1);
await todoItem.dblclick();
await expect(todoItem.getByRole('checkbox')).not.toBeVisible();
await expect(todoItem.locator('label', {
hasText: TODO_ITEMS[1],
})).not.toBeVisible();
await checkNumberOfTodosInLocalStorage(page, 3);
});
test('should save edits on blur', async ({ page }) => {
const todoItems = page.getByTestId('todo-item');
await todoItems.nth(1).dblclick();
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2],
]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
test('should trim entered text', async ({ page }) => {
const todoItems = page.getByTestId('todo-item');
await todoItems.nth(1).dblclick();
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages ');
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
'buy some sausages',
TODO_ITEMS[2],
]);
await checkTodosInLocalStorage(page, 'buy some sausages');
});
test('should remove the item if an empty text string was entered', async ({ page }) => {
const todoItems = page.getByTestId('todo-item');
await todoItems.nth(1).dblclick();
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('');
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter');
await expect(todoItems).toHaveText([
TODO_ITEMS[0],
TODO_ITEMS[2],
]);
});
test('should cancel edits on escape', async ({ page }) => {
const todoItems = page.getByTestId('todo-item');
await todoItems.nth(1).dblclick();
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages');
await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape');
await expect(todoItems).toHaveText(TODO_ITEMS);
});
});
test.describe('Counter', () => {
test('should display the current number of todo items', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
// create a todo count locator
const todoCount = page.getByTestId('todo-count')
await newTodo.fill(TODO_ITEMS[0]);
await newTodo.press('Enter');
await expect(todoCount).toContainText('1');
await newTodo.fill(TODO_ITEMS[1]);
await newTodo.press('Enter');
await expect(todoCount).toContainText('2');
await checkNumberOfTodosInLocalStorage(page, 2);
});
});
test.describe('Clear completed button', () => {
test.beforeEach(async ({ page }) => {
await createDefaultTodos(page);
});
test('should display the correct text', async ({ page }) => {
await page.locator('.todo-list li .toggle').first().check();
await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible();
});
test('should remove completed items when clicked', async ({ page }) => {
const todoItems = page.getByTestId('todo-item');
await todoItems.nth(1).getByRole('checkbox').check();
await page.getByRole('button', { name: 'Clear completed' }).click();
await expect(todoItems).toHaveCount(2);
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
});
test('should be hidden when there are no items that are completed', async ({ page }) => {
await page.locator('.todo-list li .toggle').first().check();
await page.getByRole('button', { name: 'Clear completed' }).click();
await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden();
});
});
test.describe('Persistence', () => {
test('should persist its data', async ({ page }) => {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
for (const item of TODO_ITEMS.slice(0, 2)) {
await newTodo.fill(item);
await newTodo.press('Enter');
}
const todoItems = page.getByTestId('todo-item');
const firstTodoCheck = todoItems.nth(0).getByRole('checkbox');
await firstTodoCheck.check();
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
await expect(firstTodoCheck).toBeChecked();
await expect(todoItems).toHaveClass(['completed', '']);
// Ensure there is 1 completed item.
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
// Now reload.
await page.reload();
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
await expect(firstTodoCheck).toBeChecked();
await expect(todoItems).toHaveClass(['completed', '']);
});
});
test.describe('Routing', () => {
test.beforeEach(async ({ page }) => {
await createDefaultTodos(page);
// make sure the app had a chance to save updated todos in storage
// before navigating to a new view, otherwise the items can get lost :(
// in some frameworks like Durandal
await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
});
test('should allow me to display active items', async ({ page }) => {
const todoItem = page.getByTestId('todo-item');
await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
await page.getByRole('link', { name: 'Active' }).click();
await expect(todoItem).toHaveCount(2);
await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
});
test('should respect the back button', async ({ page }) => {
const todoItem = page.getByTestId('todo-item');
await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
await test.step('Showing all items', async () => {
await page.getByRole('link', { name: 'All' }).click();
await expect(todoItem).toHaveCount(3);
});
await test.step('Showing active items', async () => {
await page.getByRole('link', { name: 'Active' }).click();
});
await test.step('Showing completed items', async () => {
await page.getByRole('link', { name: 'Completed' }).click();
});
await expect(todoItem).toHaveCount(1);
await page.goBack();
await expect(todoItem).toHaveCount(2);
await page.goBack();
await expect(todoItem).toHaveCount(3);
});
test('should allow me to display completed items', async ({ page }) => {
await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
await page.getByRole('link', { name: 'Completed' }).click();
await expect(page.getByTestId('todo-item')).toHaveCount(1);
});
test('should allow me to display all items', async ({ page }) => {
await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check();
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
await page.getByRole('link', { name: 'Active' }).click();
await page.getByRole('link', { name: 'Completed' }).click();
await page.getByRole('link', { name: 'All' }).click();
await expect(page.getByTestId('todo-item')).toHaveCount(3);
});
test('should highlight the currently applied filter', async ({ page }) => {
await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected');
//create locators for active and completed links
const activeLink = page.getByRole('link', { name: 'Active' });
const completedLink = page.getByRole('link', { name: 'Completed' });
await activeLink.click();
// Page change - active items.
await expect(activeLink).toHaveClass('selected');
await completedLink.click();
// Page change - completed items.
await expect(completedLink).toHaveClass('selected');
});
});
async function createDefaultTodos(page: Page) {
// create a new todo locator
const newTodo = page.getByPlaceholder('What needs to be done?');
for (const item of TODO_ITEMS) {
await newTodo.fill(item);
await newTodo.press('Enter');
}
}
async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
return await page.waitForFunction(e => {
return JSON.parse(localStorage['react-todos']).length === e;
}, expected);
}
async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {
return await page.waitForFunction(e => {
return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e;
}, expected);
}
async function checkTodosInLocalStorage(page: Page, title: string) {
return await page.waitForFunction(t => {
return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
}, title);
}