diff --git a/.babelrc b/.babelrc deleted file mode 100644 index c689706..0000000 --- a/.babelrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - [ - "@babel/preset-react", - { - "runtime": "automatic" - } - ], - "@babel/preset-typescript" - ], - "plugins": [ - [ - "@babel/plugin-transform-runtime", - { - "regenerator": true - } - ] - ] -} diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9b62684 --- /dev/null +++ b/.env.test @@ -0,0 +1 @@ +TICKER_API_URL=http://localhost:8080/v1 diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..ec5bf9d --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended'], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, +} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 17c3430..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "env": { - "browser": true, - "jest": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - } - }, - "plugins": [ - "react", - "react-hooks", - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "error", - "no-console": "error", - "react/jsx-no-bind": "off", - "react/jsx-sort-props": [ - "warn", - { - "reservedFirst": true - } - ], - "react/prop-types": "error", - "react/react-in-jsx-scope": "off", - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "error" - }, - "settings": { - "react": { - "version": "detect" - } - } -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1d2b526..ff21202 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,37 +13,33 @@ updates: groups: react: patterns: + - "@semantic-ui-react/css-patch" - "@types/react" - "@types/react-helmet" + - "@types/styled-components" + - "dayjs" + - "leaflet" + - "pure-react-carousel" - "react" - "react-*" + - "semantic-ui-*" + - "styled-components" development: patterns: - - "@babel/*" - "@testing-library/*" - - "@types/*" + - "@types/node" + - "@types/react-dom" - "@typescript-eslint/*" - - "babel-*" - - "css-loader" - - "dotenv" - - "dotenv-*" + - "@vitejs/*" + - "@vitest/*" + - "cssnano" - "eslint" - "eslint-*" - - "file-loader" - - "html-webpack-plugin" - - "identity-obj-proxy" - - "jest" - - "jest-*" + - "jsdom" - "postcss" - "postcss-*" - "prettier" - - "prettier-*" - - "resolve" - - "resolove-url-loader" - - "style-loader" - - "terser-webpack-plugin" - - "ts-*" - "typescript" - - "typescript-*" - - "webpack" - - "webpack-*" + - "vite" + - "vitest" + - "vitest-*" diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 9455d19..c3ca50a 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -24,7 +24,7 @@ jobs: run: yarn install --frozen-lockfile - name: Test - run: yarn run test --coverage + run: yarn run coverage - name: Codecov uses: codecov/codecov-action@v3 diff --git a/README.md b/README.md index 09d8188..804f248 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# ticker-frontend +# ticker-frontend [![Integration](https://github.com/systemli/ticker-frontend/actions/workflows/integration.yaml/badge.svg)](https://github.com/systemli/ticker-frontend/actions/workflows/integration.yaml) [![codecov](https://codecov.io/gh/systemli/ticker-frontend/branch/main/graph/badge.svg?token=bjZUlRawuh)](https://codecov.io/gh/systemli/ticker-frontend) ## Development -**Requirement:** Running instance of [ticker](https://github.com/systemli/ticker), default: http://localhost:8080/v1 +**Requirement:** Running instance of [ticker](https://github.com/systemli/ticker), default: -``` +```shell # Install dependencies yarn # Start development server (http://localhost:4000) -yarn start +yarn run dev ``` ## Configuration Place configuration in `.env` file and restart/rebuild the ticker-admin -``` -REACT_APP_API_URL=http://localhost:8080/v1 +```shell +TICKER_API_URL=http://localhost:8080/v1 ``` diff --git a/public/index.html b/index.html similarity index 60% rename from public/index.html rename to index.html index c24c310..fc0dfb6 100644 --- a/public/index.html +++ b/index.html @@ -3,12 +3,11 @@ + Ticker - -
+
+ diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index 544a256..0000000 --- a/jest.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Config } from '@jest/types' - -const config: Config.InitialOptions = { - verbose: false, - testEnvironment: 'jsdom', - preset: 'ts-jest', - moduleNameMapper: { - 'react-markdown': '/src/__mocks_/react-markdown.js', - '^.+.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': - 'jest-transform-stub', - }, - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - '^.+\\.(js|jsx)$': 'babel-jest', - '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': - 'jest-transform-stub', - }, - transformIgnorePatterns: ['/node_modules/(?!react-markdown/)'], - setupFilesAfterEnv: ['./jest-setup.ts'], -} -export default config diff --git a/package.json b/package.json index 21965c6..d0275a7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "ticker-frontend", "private": true, + "type": "module", "scripts": { - "start": "webpack serve --config webpack.dev.config.ts", - "build": "webpack --config webpack.prod.config.ts", - "test": "jest", - "lint": "eslint --ext=ts,tsx src", + "build": "vite build", + "dev": "vite", + "preview": "vite preview", + "test": "vitest", + "coverage": "vitest run --coverage", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "postinstall": "semantic-ui-css-patch" }, "dependencies": { @@ -19,86 +22,38 @@ "pure-react-carousel": "^1.30.1", "react": "^18.2.0", "react-app-polyfill": "^3.0.0", + "react-dom": "^18.2.0", "react-helmet": "^6.1.0", "react-leaflet": "^3.1.0", "react-markdown": "^8.0.1", "react-refresh": "^0.14.0", "semantic-ui-css": "^2.5.0", "semantic-ui-react": "^2.1.2", - "styled-components": "^5.2.3", - "typescript": "^5.3.3" + "styled-components": "^5.2.3" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/plugin-transform-runtime": "^7.23.7", - "@babel/preset-env": "^7.23.7", - "@babel/preset-react": "^7.23.3", - "@babel/preset-typescript": "^7.23.3", "@testing-library/dom": "^9.3.3", "@testing-library/jest-dom": "6.1.6", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", - "@types/html-webpack-plugin": "^3.2.9", - "@types/jest": "^29.5.11", "@types/node": "^20.10.6", - "@types/react-dom": "^18.2.18", + "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/parser": "^6.17.0", - "babel-eslint": "^10.1.0", - "babel-jest": "^29.7.0", - "babel-loader": "^9.1.3", - "babel-plugin-named-asset-import": "^0.3.7", - "babel-plugin-styled-components": "^2.1.4", - "babel-preset-react-app": "^10.0.0", - "css-loader": "^6.8.1", - "cssnano": "^6.0.2", - "dotenv": "16.3.1", - "dotenv-expand": "10.0.0", - "dotenv-webpack": "^8.0.1", + "@vitejs/plugin-react": "^4.2.1", + "@vitest/coverage-v8": "^1.1.3", + "cssnano": "^6.0.3", "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-config-react-app": "^7.0.1", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jest": "^27.6.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-prettier": "^5.1.2", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-testing-library": "^6.2.0", - "eslint-webpack-plugin": "^4.0.1", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.6.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^29.7.0", - "jest-dom": "^4.0.0", - "jest-environment-jsdom": "^29.7.0", - "jest-fetch-mock": "^3.0.3", - "jest-transform-stub": "^2.0.0", + "eslint-plugin-react-refresh": "^0.4.5", + "jsdom": "^23.2.0", "postcss": "^8.4.32", - "postcss-loader": "^7.3.4", "postcss-remove-google-fonts": "^1.2.3", "prettier": "^3.1.1", - "react-dev-utils": "^12.0.1", - "react-dom": "^18.2.0", - "resolve": "^1.22.0", - "resolve-url-loader": "^5.0.0", - "semver": "^7.5.2", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.3.10", - "ts-jest": "^29.1.1", - "ts-loader": "^9.5.1", - "ts-node": "^10.9.2", - "ts-pnp": "^1.2.0", - "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", - "webpack-manifest-plugin": "^5.0.0", - "webpack-merge": "^5.10.0" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not op_mini all" - ] + "typescript": "^5.3.3", + "vite": "^5.0.11", + "vitest": "^1.1.3", + "vitest-fetch-mock": "^0.2.2" + } } diff --git a/postcss.config.js b/postcss.config.cjs similarity index 100% rename from postcss.config.js rename to postcss.config.cjs diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index c2a49f4..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Allow: / diff --git a/src/Ticker.test.tsx b/src/Ticker.test.tsx index f7a40ba..112146f 100644 --- a/src/Ticker.test.tsx +++ b/src/Ticker.test.tsx @@ -3,6 +3,7 @@ import { render, screen } from '@testing-library/react' import * as api from './lib/api' import { Settings, Ticker as TickerType } from './lib/types' import { TickerProvider } from './components/useTicker' +import { vi } from 'vitest' describe('Ticker', function () { const initSettings = { @@ -41,7 +42,7 @@ describe('Ticker', function () { } test('renders OfflineView', async function () { - jest.spyOn(api, 'getInit').mockRejectedValue(new TypeError()) + vi.spyOn(api, 'getInit').mockRejectedValue(new TypeError()) renderTicker() expect(screen.getByText('Loading')).toBeInTheDocument() @@ -50,7 +51,7 @@ describe('Ticker', function () { }) test('renders ErrorView', async function () { - jest.spyOn(api, 'getInit').mockRejectedValue(new Error('The server responses with an error: Internal Server Error (500)')) + vi.spyOn(api, 'getInit').mockRejectedValue(new Error('The server responses with an error: Internal Server Error (500)')) renderTicker() expect(screen.getByText('Loading')).toBeInTheDocument() @@ -59,7 +60,7 @@ describe('Ticker', function () { }) test('renders InactiveView', async function () { - jest.spyOn(api, 'getInit').mockResolvedValue({ + vi.spyOn(api, 'getInit').mockResolvedValue({ data: { settings: initSettings, ticker: null, @@ -73,13 +74,13 @@ describe('Ticker', function () { }) test('renders ActiveView', async function () { - jest.spyOn(api, 'getInit').mockResolvedValue({ + vi.spyOn(api, 'getInit').mockResolvedValue({ data: { settings: initSettings, ticker: ticker, }, }) - jest.spyOn(api, 'getTimeline').mockResolvedValue({ + vi.spyOn(api, 'getTimeline').mockResolvedValue({ data: { messages: [], }, @@ -87,7 +88,7 @@ describe('Ticker', function () { const intersectionObserverMock = () => ({ observe: () => null, }) - window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock) + window.IntersectionObserver = vi.fn().mockImplementation(intersectionObserverMock) renderTicker() expect(screen.getByText('Loading')).toBeInTheDocument() diff --git a/src/__mocks__/react-markdown.js b/src/__mocks__/react-markdown.js index 47e9e9f..e6c754a 100644 --- a/src/__mocks__/react-markdown.js +++ b/src/__mocks__/react-markdown.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line react/prop-types export default function ReactMarkdown({ children }) { return <>{children} } diff --git a/src/components/Map.tsx b/src/components/Map.tsx index 89ba1e5..796dcd8 100644 --- a/src/components/Map.tsx +++ b/src/components/Map.tsx @@ -54,11 +54,10 @@ const Map: FC = props => { if ( features.length === 1 && - // Type is currently not defined by DefinitelyTyped - // @ts-ignore + // @ts-expect-error Type is currently not defined by DefinitelyTyped features[0].feature.geometry.type === 'Point' ) { - // @ts-ignore + // @ts-expect-error Type is currently not defined by DefinitelyTyped const coords = features[0].feature.geometry.coordinates leafletLayer._map.setView([coords[1], coords[0]], 13) } else { diff --git a/src/components/MessageList.test.tsx b/src/components/MessageList.test.tsx index 13e7140..ef151ff 100644 --- a/src/components/MessageList.test.tsx +++ b/src/components/MessageList.test.tsx @@ -1,16 +1,17 @@ import * as api from '../lib/api' import MessageList from './MessageList' import { render, screen } from '@testing-library/react' +import { vi } from 'vitest' describe('MessageList', function () { test('renders empty Messages', async function () { - jest.spyOn(api, 'getTimeline').mockResolvedValue({ + vi.spyOn(api, 'getTimeline').mockResolvedValue({ data: { messages: [] }, }) const intersectionObserverMock = () => ({ observe: () => null, }) - window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock) + window.IntersectionObserver = vi.fn().mockImplementation(intersectionObserverMock) render() diff --git a/src/components/MessageList.tsx b/src/components/MessageList.tsx index e0c587a..abcd121 100644 --- a/src/components/MessageList.tsx +++ b/src/components/MessageList.tsx @@ -24,9 +24,7 @@ const MessageList: FC = () => { } setIsLoading(false) }) - .catch(error => { - // eslint-disable-next-line no-console - console.error(error) + .catch(() => { setIsLoading(false) }) }, [messages]) @@ -42,10 +40,7 @@ const MessageList: FC = () => { setLastMessageReceived(true) } }) - .catch(error => { - // eslint-disable-next-line no-console - console.error(error) - }) + .catch(() => {}) } }, [messages]) diff --git a/src/components/__snapshots__/Attachments.test.tsx.snap b/src/components/__snapshots__/Attachments.test.tsx.snap index 483170f..3c1b56f 100644 --- a/src/components/__snapshots__/Attachments.test.tsx.snap +++ b/src/components/__snapshots__/Attachments.test.tsx.snap @@ -1,4 +1,165 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Attachment > renders multiple images as slider 1`] = ` + +