Compare commits
14 Commits
answer-key
...
live-codin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b93f0ac4b | ||
|
|
9d5615cb9d | ||
|
|
a9e36ee3c8 | ||
|
|
8fb10ac976 | ||
|
|
25f1a63b96 | ||
|
|
2552cb26d5 | ||
|
|
9cb1a0c739 | ||
|
|
579a55a4ae | ||
|
|
a3b3d154c3 | ||
|
|
058fe1fdc8 | ||
|
|
9e2cfcee70 | ||
|
|
672d11c88b | ||
|
|
c1fe4faa03 | ||
|
|
90b96aa61a |
@@ -1,4 +1,4 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, act } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { Counter } from './counter';
|
import { Counter } from './counter';
|
||||||
@@ -6,36 +6,96 @@ import { Counter } from './counter';
|
|||||||
import '@testing-library/jest-dom/vitest';
|
import '@testing-library/jest-dom/vitest';
|
||||||
|
|
||||||
describe('Counter ', () => {
|
describe('Counter ', () => {
|
||||||
beforeEach(() => {
|
|
||||||
render(<Counter />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders with an initial count of 0', () => {
|
it('renders with an initial count of 0', () => {
|
||||||
const countElement = screen.getByTestId('counter-count');
|
render(<Counter />);
|
||||||
expect(countElement).toHaveTextContent('0');
|
const counter = screen.getByTestId('counter-count');
|
||||||
|
expect(counter).toHaveTextContent('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables the "Decrement" and "Reset" buttons when the count is 0', () => {
|
it('disables the "Decrement" and "Reset" buttons when the count is 0', () => {
|
||||||
const decrementButton = screen.getByRole('button', { name: 'Decrement' });
|
render(<Counter />);
|
||||||
const resetButton = screen.getByRole('button', { name: 'Reset' });
|
const decrementButton = screen.getByRole('button', { name: /decrement/i });
|
||||||
|
const resetButton = screen.getByRole('button', { name: /reset/i });
|
||||||
|
|
||||||
expect(decrementButton).toBeDisabled();
|
expect(decrementButton).toBeDisabled();
|
||||||
expect(resetButton).toBeDisabled();
|
expect(resetButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.todo('displays "days" when the count is 0', () => {});
|
it('displays "days" when the count is 0', () => {
|
||||||
|
render(<Counter />);
|
||||||
it.todo('increments the count when the "Increment" button is clicked', async () => {});
|
const unit = screen.getByTestId('counter-unit');
|
||||||
|
expect(unit).toHaveTextContent('days');
|
||||||
it.todo('displays "day" when the count is 1', async () => {});
|
});
|
||||||
|
|
||||||
it.todo('decrements the count when the "Decrement" button is clicked', async () => {});
|
it('increments the count when the "Increment" button is clicked', async () => {
|
||||||
|
render(<Counter />);
|
||||||
it.todo('does not allow decrementing below 0', async () => {});
|
const incrementButton = screen.getByRole('button', { name: /increment/i });
|
||||||
|
const counter = screen.getByTestId('counter-count');
|
||||||
it.todo('resets the count when the "Reset" button is clicked', async () => {});
|
|
||||||
|
await act(async () => {
|
||||||
it.todo('disables the "Decrement" and "Reset" buttons when the count is 0', () => {});
|
await userEvent.click(incrementButton);
|
||||||
|
});
|
||||||
it.todo('updates the document title based on the count', async () => {});
|
|
||||||
|
expect(counter).toHaveTextContent('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays "day" when the count is 1', async () => {
|
||||||
|
render(<Counter />);
|
||||||
|
const incrementButton = screen.getByRole('button', { name: /increment/i });
|
||||||
|
const unit = screen.getByTestId('counter-unit');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(incrementButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unit).toHaveTextContent('day');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decrements the count when the "Decrement" button is clicked', async () => {
|
||||||
|
render(<Counter initialCount={1} />);
|
||||||
|
const decrementButton = screen.getByRole('button', { name: /decrement/i });
|
||||||
|
const count = screen.getByTestId('counter-count');
|
||||||
|
|
||||||
|
expect(decrementButton).not.toBeDisabled();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(decrementButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(count).toHaveTextContent('0');
|
||||||
|
expect(decrementButton).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow decrementing below 0', async () => {
|
||||||
|
render(<Counter />);
|
||||||
|
const decrementButton = screen.getByRole('button', { name: /decrement/i });
|
||||||
|
const count = screen.getByTestId('counter-count');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(decrementButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(count).toHaveTextContent('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.todo(
|
||||||
|
'resets the count when the "Reset" button is clicked',
|
||||||
|
async () => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
it.todo(
|
||||||
|
'disables the "Decrement" and "Reset" buttons when the count is 0',
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
it('updates the document title based on the count', async () => {
|
||||||
|
const { getByRole } = render(<Counter />);
|
||||||
|
const incrementButton = getByRole('button', { name: /increment/i });
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(incrementButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(document.title).toEqual(expect.stringContaining('1 day'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import { useReducer, useEffect } from 'react';
|
import { useReducer, useEffect } from 'react';
|
||||||
import { reducer } from './reducer';
|
import { reducer } from './reducer';
|
||||||
|
|
||||||
export const Counter = () => {
|
export const Counter = ({ initialCount = 0 }) => {
|
||||||
const [state, dispatch] = useReducer(reducer, { count: 0 });
|
const [state, dispatch] = useReducer(reducer, { count: initialCount });
|
||||||
const unit = state.count === 1 ? 'day' : 'days';
|
const unit = state.count === 1 ? 'day' : 'days';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto('http://localhost:5173');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it has a counter', async ({ page }) => {
|
|
||||||
const count = page.getByTestId('counter-count');
|
|
||||||
const incrementButton = page.getByRole('button', { name: /increment/i });
|
|
||||||
|
|
||||||
await incrementButton.click();
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,28 @@
|
|||||||
export const add = () => {};
|
export const add = (a, b) => {
|
||||||
|
if (typeof a === 'string') a = Number(a);
|
||||||
|
if (typeof b === 'string') b = Number(b);
|
||||||
|
|
||||||
export const subtract = () => {};
|
if (isNaN(a)) throw new Error('The first argument is not a number');
|
||||||
|
if (isNaN(b)) throw new Error('The second argument is not a number');
|
||||||
|
|
||||||
export const multiply = () => {};
|
return a + b;
|
||||||
|
};
|
||||||
|
|
||||||
export const divide = () => {};
|
export const subtract = (a = 0, b = 0) => {
|
||||||
|
if (Array.isArray(a)) {
|
||||||
|
a = a.reduce((a, b) => {
|
||||||
|
return a - b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return a - b;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const multiply = (a, b) => {
|
||||||
|
return a * b;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const divide = (a, b) => {
|
||||||
|
if (b === 0) return null;
|
||||||
|
return a / b;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,9 +1,72 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { add, subtract, multiply, divide } from './arithmetic.js';
|
||||||
|
|
||||||
describe.todo('add', () => {});
|
describe('add', () => {
|
||||||
|
it('should add two positive numbers', () => {
|
||||||
|
expect(add(2, 2)).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
describe.todo('subtract', () => {});
|
it('should add two negative numbers', () => {
|
||||||
|
expect(add(-2, -2)).toBe(-4);
|
||||||
|
});
|
||||||
|
|
||||||
describe.todo('multiply', () => {});
|
it('should parse strings into numbers', () => {
|
||||||
|
expect(add('1', '1')).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
describe.todo('divide', () => {});
|
it('should get real angry if you give it a first argument that cannot be parsed into a number', () => {
|
||||||
|
expect(() => add('potato', 2)).toThrow('not a number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get real angry if you give it a second argument that cannot be parsed into a number', () => {
|
||||||
|
expect(() => add(2, 'potato')).toThrow('not a number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the first argument is not a number', () => {
|
||||||
|
expect(() => add(NaN, 2)).toThrow('not a number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle floating point math as best it can', () => {
|
||||||
|
expect(add(1.0000001, 2.0000004)).toBeCloseTo(3.0, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subtract', () => {
|
||||||
|
it('should subtract one number from the other', () => {
|
||||||
|
expect(subtract(4, 2)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept and subtract all of the numbers', () => {
|
||||||
|
expect(subtract([10, 5], 2)).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default undefined values to 0', () => {
|
||||||
|
expect(subtract(3)).toBe(3);
|
||||||
|
expect(subtract(undefined, 3)).toBe(-3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default to zero if either argument is null', () => {
|
||||||
|
expect(subtract(3, null)).toBe(3);
|
||||||
|
expect(subtract(null, 3)).toBe(-3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('multiply', () => {
|
||||||
|
it('should multiply two numbers', () => {
|
||||||
|
expect(multiply(3, 2)).toBe(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('divide', () => {
|
||||||
|
it('should divide two numbers', () => {
|
||||||
|
expect(divide(10, 2)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if dividing by zero', () => {
|
||||||
|
expect(divide(10, 0)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return zero if dividing by Infinity', () => {
|
||||||
|
expect(divide(10, Infinity)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { Character } from './character.js';
|
|
||||||
import { Person } from './person.js';
|
|
||||||
|
|
||||||
describe('Character', () => {
|
|
||||||
it.todo(
|
|
||||||
'should create a character with a first name, last name, and role',
|
|
||||||
() => {},
|
|
||||||
);
|
|
||||||
|
|
||||||
it.todo('should allow you to increase the level', () => {});
|
|
||||||
|
|
||||||
it.todo('should update the last modified date when leveling up', () => {});
|
|
||||||
});
|
|
||||||
@@ -2,21 +2,21 @@ import { Person } from './person.js';
|
|||||||
import { rollDice } from './roll-dice.js';
|
import { rollDice } from './roll-dice.js';
|
||||||
|
|
||||||
export class Character extends Person {
|
export class Character extends Person {
|
||||||
constructor(firstName, lastName, role) {
|
constructor(firstName, lastName, role, level = 1, roll = rollDice) {
|
||||||
super(firstName, lastName);
|
super(firstName, lastName);
|
||||||
|
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.level = 1;
|
this.level = level;
|
||||||
|
|
||||||
this.createdAt = new Date();
|
this.createdAt = new Date();
|
||||||
this.lastModified = this.createdAt;
|
this.lastModified = this.createdAt;
|
||||||
|
|
||||||
this.strength = rollDice(4, 6);
|
this.strength = roll(4, 6);
|
||||||
this.dexterity = rollDice(4, 6);
|
this.dexterity = roll(4, 6);
|
||||||
this.intelligence = rollDice(4, 6);
|
this.intelligence = roll(4, 6);
|
||||||
this.wisdom = rollDice(4, 6);
|
this.wisdom = roll(4, 6);
|
||||||
this.charisma = rollDice(4, 6);
|
this.charisma = roll(4, 6);
|
||||||
this.constitution = rollDice(4, 6);
|
this.constitution = roll(4, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
levelUp() {
|
levelUp() {
|
||||||
|
|||||||
60
examples/characters/src/character.test.ts
Normal file
60
examples/characters/src/character.test.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { Character } from './character.js';
|
||||||
|
import { Person } from './person.js';
|
||||||
|
|
||||||
|
const firstName = 'Ada';
|
||||||
|
const lastName = 'Lovelace';
|
||||||
|
const role = 'Computer Scienst';
|
||||||
|
|
||||||
|
describe('Character', () => {
|
||||||
|
let character;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
character = new Character(firstName, lastName, role, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('should create a character with a first name, last name, and role', () => {
|
||||||
|
expect(character).toEqual({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
role,
|
||||||
|
strength: 12,
|
||||||
|
wisdom: 12,
|
||||||
|
dexterity: 12,
|
||||||
|
intelligence: 12,
|
||||||
|
constitution: 12,
|
||||||
|
charisma: 12,
|
||||||
|
level: 1,
|
||||||
|
lastModified: expect.any(Date),
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
id: expect.stringContaining('person-'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow you to increase the level', () => {
|
||||||
|
const initialLevel = character.level;
|
||||||
|
|
||||||
|
character.levelUp();
|
||||||
|
|
||||||
|
expect(character.level).toBeGreaterThan(initialLevel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the last modified date when leveling up', () => {
|
||||||
|
const initialLastModified = character.lastModified;
|
||||||
|
|
||||||
|
character.levelUp();
|
||||||
|
|
||||||
|
expect(character.lastModified).not.toBe(initialLastModified);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only('should roll four six-sided die', () => {
|
||||||
|
const rollDiceMock = vi.fn(() => 15);
|
||||||
|
const character = new Character(firstName, lastName, role, 1, rollDiceMock);
|
||||||
|
|
||||||
|
expect(character.strength).toBe(15);
|
||||||
|
expect(rollDiceMock).toHaveBeenCalledWith(4, 6);
|
||||||
|
expect(rollDiceMock).toHaveBeenCalledTimes(6);
|
||||||
|
|
||||||
|
console.log(rollDiceMock.mock.calls);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,11 +2,12 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { Person } from './person.js';
|
import { Person } from './person.js';
|
||||||
|
|
||||||
// Remove the `todo` from the `describe` to run the tests.
|
// Remove the `todo` from the `describe` to run the tests.
|
||||||
describe.todo('Person', () => {
|
describe('Person', () => {
|
||||||
// This test will fail. Why?
|
// This test will fail. Why?
|
||||||
it('should create a person with a first name and last name', () => {
|
it('should create a person with a first name and last name', () => {
|
||||||
const person = new Person('Grace', 'Hopper');
|
const person = new Person('Grace', 'Hopper');
|
||||||
expect(person).toEqual({
|
expect(person).toEqual({
|
||||||
|
id: expect.stringContaining('person-'),
|
||||||
firstName: 'Grace',
|
firstName: 'Grace',
|
||||||
lastName: 'Hopper',
|
lastName: 'Hopper',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export const AlertButton = ({}) => {
|
export const AlertButton = ({
|
||||||
const [message, setMessage] = useState('Alert!');
|
onSubmit = () => {},
|
||||||
|
defaultMessage = 'Alert!',
|
||||||
|
}) => {
|
||||||
|
const [message, setMessage] = useState(defaultMessage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -14,7 +17,7 @@ export const AlertButton = ({}) => {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button onClick={() => alert(message)}>Trigger Alert</button>
|
<button onClick={() => onSubmit(message)}>Trigger Alert</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,31 +4,27 @@ import userEvent from '@testing-library/user-event';
|
|||||||
import { AlertButton } from './alert-button';
|
import { AlertButton } from './alert-button';
|
||||||
|
|
||||||
describe('AlertButton', () => {
|
describe('AlertButton', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {});
|
||||||
vi.spyOn(window, 'alert').mockImplementation(() => {});
|
|
||||||
render(<AlertButton />);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {});
|
||||||
vi.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render an alert button', async () => {
|
it('should render an alert button', async () => {});
|
||||||
|
|
||||||
|
it.only('should trigger an alert', async () => {
|
||||||
|
const handleSubmit = vi.fn();
|
||||||
|
|
||||||
|
render(<AlertButton onSubmit={handleSubmit} message="Default Message" />);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Message');
|
||||||
const button = screen.getByRole('button', { name: /trigger alert/i });
|
const button = screen.getByRole('button', { name: /trigger alert/i });
|
||||||
|
|
||||||
expect(button).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trigger an alert', async () => {
|
|
||||||
const button = screen.getByRole('button', { name: /trigger alert/i });
|
|
||||||
const messageInput = screen.getByLabelText(/message/i);
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await userEvent.clear(messageInput);
|
await userEvent.clear(input);
|
||||||
await userEvent.type(messageInput, 'Hello, world!');
|
await userEvent.type(input, 'Hello');
|
||||||
await userEvent.click(button);
|
await userEvent.click(button);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(window.alert).toHaveBeenCalledWith('Hello, world!');
|
expect(handleSubmit).toHaveBeenCalled();
|
||||||
|
expect(handleSubmit).toHaveBeenCalledWith('Hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
|
import { screen, fireEvent } from '@testing-library/dom';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import { createButton } from './button.js';
|
import { createButton } from './button.js';
|
||||||
|
|
||||||
describe('createButton', () => {
|
describe('createButton', () => {
|
||||||
it('should create a button element', () => {
|
beforeEach(() => {
|
||||||
const button = createButton();
|
document.innerHTML = '';
|
||||||
expect(button.tagName).toBe('BUTTON');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the text "Click Me"', () => {
|
it.skip('should create a button element', () => {
|
||||||
const button = createButton();
|
document.body.appendChild(createButton());
|
||||||
expect(button.textContent).toBe('Click Me');
|
|
||||||
|
const button = screen.getByRole('button', { name: 'Click Me' });
|
||||||
|
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change the text to "Clicked!" when clicked', async () => {
|
it('should change the text to "Clicked!" when clicked', async () => {
|
||||||
const button = createButton();
|
document.body.appendChild(createButton());
|
||||||
button.click();
|
const button = screen.getByRole('button', { name: 'Click Me' });
|
||||||
|
|
||||||
|
await userEvent.click(button);
|
||||||
|
|
||||||
expect(button.textContent).toBe('Clicked!');
|
expect(button.textContent).toBe('Clicked!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,45 +2,20 @@ import { screen } from '@testing-library/dom';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { createLoginForm } from './login-form';
|
import { createLoginForm } from './login-form';
|
||||||
|
|
||||||
describe('LoginForm', async () => {
|
describe.todo('Login Form', async () => {
|
||||||
it('should render a login form', async () => {
|
it('should render a login form', async () => {});
|
||||||
document.body.replaceChildren(createLoginForm());
|
|
||||||
|
|
||||||
const form = screen.getByRole('form', { name: /login/i });
|
|
||||||
|
|
||||||
expect(form).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render a login form with a custom action', async () => {
|
it('should render a login form with a custom action', async () => {
|
||||||
// Can you make sure that the form we render has an `action` attribute set to '/custom'?
|
// Can you make sure that the form we render has an `action` attribute set to '/custom'?
|
||||||
document.body.replaceChildren(createLoginForm({ action: '/custom' }));
|
|
||||||
|
|
||||||
const form = screen.getByRole('form', { name: /login/i });
|
|
||||||
|
|
||||||
expect(form).toHaveAttribute('action', '/custom');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a login form with a custom method', async () => {
|
it('should render a login form with a custom method', async () => {
|
||||||
// Can you make sure that the form we render has a `method` attribute set to 'get'?
|
// Can you make sure that the form we render has a `method` attribute set to 'get'?
|
||||||
document.body.replaceChildren(createLoginForm({ method: 'get' }));
|
|
||||||
|
|
||||||
const form = screen.getByRole('form', { name: /login/i });
|
|
||||||
|
|
||||||
expect(form).toHaveAttribute('method', 'get');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a login form with a custom submit handler', async () => {
|
it('should render a login form with a custom submit handler', async () => {
|
||||||
// We'll do this one later. Don't worry about it for now.
|
// We'll do this one later. Don't worry about it for now.
|
||||||
// If it *is* later, then you should worry about it.
|
// If it *is* later, then you should worry about it.
|
||||||
// Can you make sure that the form we render has a submit handler that calls a custom function?
|
// Can you make sure that the form we render has a submit handler that calls a custom function?
|
||||||
const onSubmit = vi.fn();
|
|
||||||
document.body.replaceChildren(createLoginForm({ onSubmit }));
|
|
||||||
|
|
||||||
const form = screen.getByRole('form', { name: /login/i });
|
|
||||||
const submitButton = screen.getByRole('button', { name: /login/i });
|
|
||||||
|
|
||||||
await userEvent.click(submitButton);
|
|
||||||
|
|
||||||
expect(onSubmit).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export const Notification = () => {
|
|
||||||
const [content, setContent] = useState('');
|
|
||||||
const [message, setMessage] = useState('');
|
|
||||||
|
|
||||||
const showNotification = () => {
|
|
||||||
console.log({ content });
|
|
||||||
if (!content) return;
|
|
||||||
setMessage(content);
|
|
||||||
setTimeout(() => setMessage(''), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
Message Content
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={content}
|
|
||||||
onChange={(event) => setContent(event.target.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button onClick={showNotification}>Show Notification</button>
|
|
||||||
|
|
||||||
{message && <p data-testid="message">{message}</p>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { vi } from 'vitest';
|
|
||||||
import { render, screen, act } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
|
|
||||||
import { Notification } from './notification';
|
|
||||||
|
|
||||||
describe('Notification', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
render(<Notification />);
|
|
||||||
vi.useFakeTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render a notification', async () => {
|
|
||||||
const input = screen.getByRole('textbox', { name: /message content/i });
|
|
||||||
const button = screen.getByRole('button', { name: /show notification/i });
|
|
||||||
|
|
||||||
expect(input).toBeInTheDocument();
|
|
||||||
expect(button).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.only('should show a notification', async () => {
|
|
||||||
const input = screen.getByRole('textbox', { name: /message content/i });
|
|
||||||
const button = screen.getByRole('button', { name: /show notification/i });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await userEvent.type(input, 'Hello, world!');
|
|
||||||
});
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await userEvent.click(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = await screen.findByTestId('message');
|
|
||||||
|
|
||||||
expect(message).toHaveTextContent('Hello, world!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not show a notification if there is no content', async () => {
|
|
||||||
const button = screen.getByRole('button', { name: /show notification/i });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await userEvent.click(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = screen.queryByTestId('message');
|
|
||||||
|
|
||||||
expect(message).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should hide a notification after 5 seconds', async () => {
|
|
||||||
const input = screen.getByRole('textbox', { name: /message content/i });
|
|
||||||
const button = screen.getByRole('button', { name: /show notification/i });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await userEvent.type(input, 'Hello, world!');
|
|
||||||
await userEvent.click(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = screen.getByTestId('message');
|
|
||||||
|
|
||||||
expect(message).toHaveTextContent('Hello, world!');
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
vi.advanceTimersByTime(5000);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(message).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,41 +4,14 @@ import '@testing-library/jest-dom/vitest';
|
|||||||
|
|
||||||
import { createSecretInput } from './secret-input.js';
|
import { createSecretInput } from './secret-input.js';
|
||||||
|
|
||||||
describe('createSecretInput', async () => {
|
describe.todo('createSecretInput', async () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {});
|
||||||
vi.spyOn(localStorage, 'getItem').mockReturnValue('test secret');
|
|
||||||
vi.spyOn(localStorage, 'setItem');
|
|
||||||
vi.spyOn(localStorage, 'removeItem');
|
|
||||||
|
|
||||||
document.body.innerHTML = '';
|
afterEach(() => {});
|
||||||
document.body.appendChild(createSecretInput());
|
|
||||||
});
|
it('should have loaded the secret from localStorage', async () => {});
|
||||||
|
|
||||||
afterEach(() => {
|
it('should save the secret to localStorage', async () => {});
|
||||||
vi.restoreAllMocks();
|
|
||||||
});
|
it('should clear the secret from localStorage', async () => {});
|
||||||
|
|
||||||
it('should have loaded the secret from localStorage', async () => {
|
|
||||||
expect(screen.getByLabelText('Secret')).toHaveValue('test secret');
|
|
||||||
expect(localStorage.getItem).toHaveBeenCalledWith('secret');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should save the secret to localStorage', async () => {
|
|
||||||
const input = screen.getByLabelText('Secret');
|
|
||||||
const button = screen.getByText('Store Secret');
|
|
||||||
|
|
||||||
await userEvent.clear(input);
|
|
||||||
await userEvent.type(input, 'new secret');
|
|
||||||
await userEvent.click(button);
|
|
||||||
|
|
||||||
expect(localStorage.setItem).toHaveBeenCalledWith('secret', 'new secret');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear the secret from localStorage', async () => {
|
|
||||||
const button = screen.getByText('Clear Secret');
|
|
||||||
|
|
||||||
await userEvent.click(button);
|
|
||||||
|
|
||||||
expect(localStorage.removeItem).toHaveBeenCalledWith('secret');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
|
|||||||
|
|
||||||
import Tabs from './tabs.svelte';
|
import Tabs from './tabs.svelte';
|
||||||
|
|
||||||
describe('Tabs', () => {
|
describe.todo('Tabs', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render(Tabs, {
|
render(Tabs, {
|
||||||
tabs: [
|
tabs: [
|
||||||
@@ -14,32 +14,11 @@ describe('Tabs', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render three tabs', async () => {
|
it('should render three tabs', async () => {});
|
||||||
const tabs = screen.getAllByRole('tab');
|
|
||||||
expect(tabs).toHaveLength(3);
|
it('should switch tabs', async () => {});
|
||||||
});
|
|
||||||
|
it('should render the content of the selected tab', async () => {});
|
||||||
it('should switch tabs', async () => {
|
|
||||||
const tabs = screen.getAllByRole('tab');
|
it('should render the content of the first tab by default', async () => {});
|
||||||
const secondTab = tabs[1];
|
|
||||||
|
|
||||||
await userEvent.click(secondTab);
|
|
||||||
|
|
||||||
expect(secondTab).toHaveAttribute('aria-selected', 'true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the content of the selected tab', async () => {
|
|
||||||
const tabs = screen.getAllByRole('tab');
|
|
||||||
const secondTab = tabs[1];
|
|
||||||
|
|
||||||
await userEvent.click(secondTab);
|
|
||||||
|
|
||||||
const content = screen.getByRole('tabpanel', { hidden: false });
|
|
||||||
expect(content).toHaveTextContent('lineup');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the content of the first tab by default', async () => {
|
|
||||||
const content = screen.getByRole('tabpanel', { hidden: false });
|
|
||||||
expect(content).toHaveTextContent('We will be at this place!');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('Game', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a secret number', () => {
|
it('should have a secret number', () => {
|
||||||
// Thisn't really a useful test.
|
// This isn't really a useful test.
|
||||||
// Do I *really* care about the type of the secret number?
|
// Do I *really* care about the type of the secret number?
|
||||||
// Do I *really* care about the name of a "private" property?
|
// Do I *really* care about the name of a "private" property?
|
||||||
const game = new Game();
|
const game = new Game();
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import { sendToServer } from './send-to-server';
|
|||||||
* Log a message to the console in development mode or send it to the server in production mode.
|
* Log a message to the console in development mode or send it to the server in production mode.
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
*/
|
*/
|
||||||
export function log(message) {
|
export function log(
|
||||||
if (import.meta.env.MODE !== 'production') {
|
message,
|
||||||
|
{ productionCallback = () => {}, mode = import.meta.env.MODE } = {},
|
||||||
|
) {
|
||||||
|
if (mode !== 'production') {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
} else {
|
} else {
|
||||||
sendToServer('info', message);
|
productionCallback('info', message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,33 @@
|
|||||||
import { expect, it, vi, beforeEach, afterEach, describe } from 'vitest';
|
import { expect, it, vi, beforeEach, afterEach, describe } from 'vitest';
|
||||||
import { log } from './log';
|
import { log } from './log';
|
||||||
|
|
||||||
describe.todo('logger', () => {});
|
describe('logger', () => {
|
||||||
|
describe('development', () => {
|
||||||
|
it('logs to the console in development mode', () => {
|
||||||
|
const logSpy = vi.fn();
|
||||||
|
|
||||||
|
log('Hello World');
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Hello World');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('production', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv('MODE', 'production');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call console.log in production', () => {
|
||||||
|
const logSpy = vi.spyOn(console, 'log');
|
||||||
|
|
||||||
|
log('Hello World', { mode: 'production', productionCallback: logSpy });
|
||||||
|
|
||||||
|
expect(logSpy).not.toHaveBeenCalled();
|
||||||
|
expect(sendToServer).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
* @param {string} message
|
* @param {string} message
|
||||||
*/
|
*/
|
||||||
export const sendToServer = (level, message) => {
|
export const sendToServer = (level, message) => {
|
||||||
|
throw new Error('I should not run!');
|
||||||
return `You must mock this function: sendToServer(${level}, ${message})`;
|
return `You must mock this function: sendToServer(${level}, ${message})`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`a super simple test 1`] = `"<div>wowowow</div>"`;
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { vi, describe, it, expect } from 'vitest';
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
function delay(callback) {
|
function delay(callback) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -9,13 +7,22 @@ function delay(callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('delay function', () => {
|
describe('delay function', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime('2024-02-29');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it('should call callback after delay', () => {
|
it('should call callback after delay', () => {
|
||||||
const callback = vi.fn();
|
const callback = vi.fn();
|
||||||
|
|
||||||
delay(callback);
|
delay(callback);
|
||||||
|
vi.advanceTimersToNextTimer();
|
||||||
|
|
||||||
vi.advanceTimersByTime(1000);
|
expect(callback).toHaveBeenCalled();
|
||||||
|
expect(new Date()).toBe(null);
|
||||||
expect(callback).toHaveBeenCalledWith('Delayed');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "scratchpad",
|
"name": "scratchpad",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -11,13 +11,4 @@ const createTask = (title) => ({
|
|||||||
lastModified: new Date('02-29-2024').toISOString(),
|
lastModified: new Date('02-29-2024').toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const handlers = [
|
export const handlers = [];
|
||||||
http.get('/api/tasks', async () => {
|
|
||||||
return HttpResponse.json(tasks);
|
|
||||||
}),
|
|
||||||
http.post('/api/tasks', async ({ request }) => {
|
|
||||||
const { title } = await request.json();
|
|
||||||
const task = createTask(title);
|
|
||||||
return HttpResponse.json(task);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
/** @type {import('../start-server').DevelopmentServer} */
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('http://localhost:5174');
|
await page.goto('http://localhost:5173');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should load the page', async ({ page }) => {
|
test('it should load the page', async ({ page }) => {
|
||||||
@@ -15,7 +16,7 @@ test('it should add a task', async ({ page }) => {
|
|||||||
await input.fill('Learn Playwright');
|
await input.fill('Learn Playwright');
|
||||||
await submit.click();
|
await submit.click();
|
||||||
|
|
||||||
const heading = page.getByRole('heading', { name: 'Learn Playwright' });
|
const heading = await page.getByRole('heading', { name: 'Learn Playwright' });
|
||||||
|
|
||||||
await expect(heading).toBeVisible();
|
await expect(heading).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,10 +6,5 @@ describe('stringToNumber', () => {
|
|||||||
expect(stringToNumber('42')).toBe(42);
|
expect(stringToNumber('42')).toBe(42);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if given a string that is not a number', () => {
|
it.todo('throws an error if given a string that is not a number', () => {});
|
||||||
const value = 'foo';
|
|
||||||
expect(() => stringToNumber(value)).toThrowError(
|
|
||||||
`cannot be parsed as a number`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user