feat: add Berlin Clock e2e tests
This commit is contained in:
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.aider*
|
||||
24
README.md
24
README.md
@@ -17,9 +17,9 @@ If you are developing a production application, we recommend updating the config
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
@@ -34,42 +34,40 @@ export default defineConfig([
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
]);
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
import reactX from "eslint-plugin-react-x";
|
||||
import reactDom from "eslint-plugin-react-dom";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
reactX.configs["recommended-typescript"],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
]);
|
||||
```
|
||||
|
||||
9
cypress.config.js
Normal file
9
cypress.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: "http://localhost:5173",
|
||||
specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
|
||||
supportFile: false,
|
||||
},
|
||||
});
|
||||
212
cypress/e2e/clock.cy.ts
Normal file
212
cypress/e2e/clock.cy.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe("Berlin Clock", () => {
|
||||
const range = Cypress._.range;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit("/");
|
||||
});
|
||||
|
||||
const enterTime = (time: string) => {
|
||||
cy.get('[data-testid="time-input"]').clear().type(time).type("{enter}");
|
||||
};
|
||||
|
||||
const getLamp = (rowTestId: string, index: number) => {
|
||||
return cy.get(`[data-testid="${rowTestId}"] [data-testid="lamp-${index}"]`);
|
||||
};
|
||||
|
||||
const assertLampColors = (rowTestId: string, expectedColors: string) => {
|
||||
range(expectedColors.length).forEach((i) => {
|
||||
getLamp(rowTestId, i).should(
|
||||
"have.attr",
|
||||
"aria-label",
|
||||
expectedColors[i],
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
describe("Seconds Lamp", () => {
|
||||
it("should show Y at 00:00:00", () => {
|
||||
enterTime("00:00:00");
|
||||
cy.get('[data-testid="seconds-lamp"]').should(
|
||||
"have.attr",
|
||||
"aria-label",
|
||||
"Y",
|
||||
);
|
||||
});
|
||||
|
||||
it("should show O at 23:59:59", () => {
|
||||
enterTime("23:59:59");
|
||||
cy.get('[data-testid="seconds-lamp"]').should(
|
||||
"have.attr",
|
||||
"aria-label",
|
||||
"O",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Five Hours Row", () => {
|
||||
it("should show OOOO at 00:00:00", () => {
|
||||
enterTime("00:00:00");
|
||||
assertLampColors("five-hours-row", "OOOO");
|
||||
});
|
||||
|
||||
it("should show RRRR at 23:59:59", () => {
|
||||
enterTime("23:59:59");
|
||||
assertLampColors("five-hours-row", "RRRR");
|
||||
});
|
||||
|
||||
it("should show OOOO at 02:04:00", () => {
|
||||
enterTime("02:04:00");
|
||||
assertLampColors("five-hours-row", "OOOO");
|
||||
});
|
||||
|
||||
it("should show ROOO at 08:23:00", () => {
|
||||
enterTime("08:23:00");
|
||||
assertLampColors("five-hours-row", "ROOO");
|
||||
});
|
||||
|
||||
it("should show RRRO at 16:35:00", () => {
|
||||
enterTime("16:35:00");
|
||||
assertLampColors("five-hours-row", "RRRO");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Single Hours Row", () => {
|
||||
it("should show OOOO at 00:00:00", () => {
|
||||
enterTime("00:00:00");
|
||||
assertLampColors("single-hours-row", "OOOO");
|
||||
});
|
||||
|
||||
it("should show RRRO at 23:59:59", () => {
|
||||
enterTime("23:59:59");
|
||||
assertLampColors("single-hours-row", "RRRO");
|
||||
});
|
||||
|
||||
it("should show RROO at 02:04:00", () => {
|
||||
enterTime("02:04:00");
|
||||
assertLampColors("single-hours-row", "RROO");
|
||||
});
|
||||
|
||||
it("should show RRRO at 08:23:00", () => {
|
||||
enterTime("08:23:00");
|
||||
assertLampColors("single-hours-row", "RRRO");
|
||||
});
|
||||
|
||||
it("should show RRRR at 14:35:00", () => {
|
||||
enterTime("14:35:00");
|
||||
assertLampColors("single-hours-row", "RRRR");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Five Minutes Row", () => {
|
||||
it("should show OOOOOOOOOOO at 00:00:00", () => {
|
||||
enterTime("00:00:00");
|
||||
assertLampColors("five-minutes-row", "OOOOOOOOOOO");
|
||||
});
|
||||
|
||||
it("should show YYRYYRYYRYY at 23:59:59", () => {
|
||||
enterTime("23:59:59");
|
||||
assertLampColors("five-minutes-row", "YYRYYRYYRYY");
|
||||
});
|
||||
|
||||
it("should show OOOOOOOOOOO at 12:04:00", () => {
|
||||
enterTime("12:04:00");
|
||||
assertLampColors("five-minutes-row", "OOOOOOOOOOO");
|
||||
});
|
||||
|
||||
it("should show YYRYOOOOOOO at 12:23:00", () => {
|
||||
enterTime("12:23:00");
|
||||
assertLampColors("five-minutes-row", "YYRYOOOOOOO");
|
||||
});
|
||||
|
||||
it("should show YYRYYRYOOOO at 12:35:00", () => {
|
||||
enterTime("12:35:00");
|
||||
assertLampColors("five-minutes-row", "YYRYYRYOOOO");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Single Minutes Row", () => {
|
||||
it("should show OOOO at 00:00:00", () => {
|
||||
enterTime("00:00:00");
|
||||
assertLampColors("single-minutes-row", "OOOO");
|
||||
});
|
||||
|
||||
it("should show YYYY at 23:59:59", () => {
|
||||
enterTime("23:59:59");
|
||||
assertLampColors("single-minutes-row", "YYYY");
|
||||
});
|
||||
|
||||
it("should show YYOO at 12:32:00", () => {
|
||||
enterTime("12:32:00");
|
||||
assertLampColors("single-minutes-row", "YYOO");
|
||||
});
|
||||
|
||||
it("should show YYYY at 12:34:00", () => {
|
||||
enterTime("12:34:00");
|
||||
assertLampColors("single-minutes-row", "YYYY");
|
||||
});
|
||||
|
||||
it("should show OOOO at 12:35:00", () => {
|
||||
enterTime("12:35:00");
|
||||
assertLampColors("single-minutes-row", "OOOO");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Full Clock Integration", () => {
|
||||
const assertFullClock = (time: string, expected: string) => {
|
||||
enterTime(time);
|
||||
|
||||
// Seconds
|
||||
cy.get('[data-testid="seconds-lamp"]').should(
|
||||
"have.attr",
|
||||
"aria-label",
|
||||
expected[0],
|
||||
);
|
||||
|
||||
// Five hours (indices 1-4)
|
||||
range(4).forEach((i) => {
|
||||
cy.get(
|
||||
`[data-testid="five-hours-row"] [data-testid="lamp-${i}"]`,
|
||||
).should("have.attr", "aria-label", expected[1 + i]);
|
||||
});
|
||||
|
||||
// Single hours (indices 5-8)
|
||||
range(4).forEach((i) => {
|
||||
cy.get(
|
||||
`[data-testid="single-hours-row"] [data-testid="lamp-${i}"]`,
|
||||
).should("have.attr", "aria-label", expected[5 + i]);
|
||||
});
|
||||
|
||||
// Five minutes (indices 9-19)
|
||||
range(11).forEach((i) => {
|
||||
cy.get(
|
||||
`[data-testid="five-minutes-row"] [data-testid="lamp-${i}"]`,
|
||||
).should("have.attr", "aria-label", expected[9 + i]);
|
||||
});
|
||||
|
||||
// Single minutes (indices 20-23)
|
||||
range(4).forEach((i) => {
|
||||
cy.get(
|
||||
`[data-testid="single-minutes-row"] [data-testid="lamp-${i}"]`,
|
||||
).should("have.attr", "aria-label", expected[20 + i]);
|
||||
});
|
||||
};
|
||||
|
||||
it("displays correct clock for 00:00:00", () => {
|
||||
assertFullClock("00:00:00", "YOOOOOOOOOOOOOOOOOOOOOOO");
|
||||
});
|
||||
|
||||
it("displays correct clock for 23:59:59", () => {
|
||||
assertFullClock("23:59:59", "ORRRRRRROYYRYYRYYRYYYYYY");
|
||||
});
|
||||
|
||||
it("displays correct clock for 16:50:06", () => {
|
||||
assertFullClock("16:50:06", "YRRROROOOYYRYYRYYRYOOOOO");
|
||||
});
|
||||
|
||||
it("displays correct clock for 11:37:01", () => {
|
||||
assertFullClock("11:37:01", "ORROOROOOYYRYYRYOOOOYYOO");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,14 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
@@ -19,4 +19,4 @@ export default defineConfig([
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
]);
|
||||
|
||||
17
package-lock.json
generated
17
package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"vite": "^8.1.0",
|
||||
@@ -5025,6 +5026,22 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz",
|
||||
"integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"vite": "^8.1.0",
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
|
||||
16
src/App.tsx
16
src/App.tsx
@@ -1,11 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from './assets/vite.svg'
|
||||
import heroImg from './assets/hero.png'
|
||||
import './App.css'
|
||||
import { useState } from "react";
|
||||
import reactLogo from "./assets/react.svg";
|
||||
import viteLogo from "./assets/vite.svg";
|
||||
import heroImg from "./assets/hero.png";
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -116,7 +116,7 @@ function App() {
|
||||
<div className="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--sans: system-ui, "Segoe UI", Roboto, sans-serif;
|
||||
--heading: system-ui, "Segoe UI", Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
|
||||
12
src/main.tsx
12
src/main.tsx
@@ -1,10 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user