a11y_agent 0.0.5.pre.alpha.3 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "todomvc-react",
3
+ "version": "1.0.0",
4
+ "description": "A TodoMVC written in React.",
5
+ "private": true,
6
+ "engines": {
7
+ "node": ">=18.13.0",
8
+ "npm": ">=8.19.3"
9
+ },
10
+ "scripts": {
11
+ "build": "webpack --config webpack.prod.js",
12
+ "dev": "webpack serve --open --config webpack.dev.js",
13
+ "serve": "http-server ./dist -p 7002 -c-1 --cors",
14
+ "test": "jest"
15
+ },
16
+ "devDependencies": {
17
+ "@babel/core": "^7.21.0",
18
+ "@babel/preset-env": "^7.20.2",
19
+ "@babel/preset-react": "^7.18.6",
20
+ "babel-loader": "^9.1.2",
21
+ "copy-webpack-plugin": "^12.0.2",
22
+ "css-loader": "^6.7.3",
23
+ "css-minimizer-webpack-plugin": "^4.2.2",
24
+ "eslint-plugin-react": "^7.32.2",
25
+ "html-webpack-plugin": "^5.5.0",
26
+ "http-server": "^14.1.1",
27
+ "mini-css-extract-plugin": "^2.7.2",
28
+ "style-loader": "^3.3.1",
29
+ "webpack": "^5.75.0",
30
+ "webpack-cli": "^5.0.1",
31
+ "webpack-dev-server": "^4.11.1",
32
+ "webpack-merge": "^5.8.0"
33
+ },
34
+ "dependencies": {
35
+ "classnames": "^2.2.5",
36
+ "react": "^17.0.2",
37
+ "react-dom": "^17.0.2",
38
+ "react-router-dom": "^6.8.2",
39
+ "todomvc-app-css": "^2.4.2",
40
+ "todomvc-common": "^1.0.5"
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-framework="react">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="description" content="A TodoMVC written in React." />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
8
+ <title>TodoMVC: React</title>
9
+ </head>
10
+ <body>
11
+ <section class="todoapp" id="root"></section>
12
+ <footer class="info">
13
+ <p>Double-click to edit a todo</p>
14
+ <p>Created by the TodoMVC Team</p>
15
+ <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
16
+ </footer>
17
+ <script src="./base.js"></script>
18
+ </body>
19
+ </html>
@@ -0,0 +1,42 @@
1
+ # TodoMVC: React
2
+
3
+ ## Description
4
+
5
+ This application uses React 17.0.2 to implement a todo application.
6
+
7
+ - [React](https://reactjs.org/) is a JavaScript library for creating user interfaces.
8
+
9
+ ## Implementation details
10
+
11
+ React focuses mainly on providing composable user interfaces to enable developers to build an appealing website or web app. React does not force the user to utilize a particular design pattern, but it does provide useful hooks to implement an MVC pattern, if desired.
12
+
13
+ React:\
14
+ Model: Todo reducer (reducer.js)\
15
+ View: React ui components\
16
+ Controller: App component + useReducer hook
17
+
18
+ MVC:\
19
+ Model: Maintains the data and behavior of an application\
20
+ View: Displays the model in the ui\
21
+ Controller: Serves as an interface between view & model components
22
+
23
+ ## Build steps
24
+
25
+ To build the static files, this application utilizes webpack. It minifies and optimizes output files and copies all necessary files to a `dist` folder.
26
+
27
+ ## Requirements
28
+
29
+ The only requirement is an installation of Node, to be able to install dependencies and run scripts to serve a local server.
30
+
31
+ ```
32
+ * Node (min version: 18.13.0)
33
+ * NPM (min version: 8.19.3)
34
+ ```
35
+
36
+ ## Local preview
37
+
38
+ ```
39
+ terminal:
40
+ 1. npm install
41
+ 2. npm run serve
42
+ ```
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { render } from "react-dom";
3
+ import { HashRouter, Route, Routes } from "react-router-dom";
4
+
5
+ import { App } from "./todo/app";
6
+ import "todomvc-app-css/index.css";
7
+ import "todomvc-common/base.css";
8
+
9
+ render(
10
+ <HashRouter>
11
+ <Routes>
12
+ <Route path="*" element={<App />} />
13
+ </Routes>
14
+ </HashRouter>,
15
+ document.getElementById("root")
16
+ );
@@ -0,0 +1,87 @@
1
+ .toggle-all {
2
+ width: 40px !important;
3
+ height: 60px !important;
4
+ right: auto !important;
5
+ }
6
+
7
+ .toggle-all-label {
8
+ pointer-events: none;
9
+ }
10
+
11
+ .todo-list {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ .todo-item {
17
+ border-bottom: 1px solid #ededed;
18
+ font-size: 24px;
19
+ position: relative;
20
+ }
21
+
22
+ .header-title {
23
+ color: #b83f45;
24
+ font-size: 80px;
25
+ font-weight: 200;
26
+ position: absolute;
27
+ text-align: center;
28
+ -webkit-text-rendering: optimizeLegibility;
29
+ -moz-text-rendering: optimizeLegibility;
30
+ text-rendering: optimizeLegibility;
31
+ top: -100px;
32
+ width: 100%;
33
+ }
34
+
35
+ .filter-item {
36
+ display: inline-block;
37
+ margin: 0 5px;
38
+ }
39
+
40
+ .filter-link {
41
+ border: 1px solid transparent;
42
+ border-radius: 3px;
43
+ color: inherit;
44
+ margin: 3px;
45
+ padding: 3px 7px;
46
+ cursor: pointer;
47
+ }
48
+
49
+ .filter-link:hover {
50
+ border-color: #db7676;
51
+ }
52
+
53
+ .filter-link.selected {
54
+ border-color: #ce4646;
55
+ }
56
+
57
+ .checkbox-wrapper {
58
+ position: relative;
59
+ width: 40px;
60
+ height: 40px;
61
+ display: inline-block;
62
+ }
63
+
64
+ .checkbox {
65
+ width: 100%;
66
+ height: 100%;
67
+ margin: 10px 0 0 15px;
68
+ cursor: pointer;
69
+ background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E");
70
+ }
71
+
72
+ .checkbox.checked {
73
+ background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E");
74
+ }
75
+
76
+ .todo-text {
77
+ display: inline-block;
78
+ padding: 15px 15px 15px 60px;
79
+ line-height: 1.2;
80
+ transition: color .4s;
81
+ color: #484848;
82
+ }
83
+
84
+ .clear-completed.disabled {
85
+ opacity: 0.5;
86
+ cursor: not-allowed;
87
+ }
@@ -0,0 +1,20 @@
1
+ import { useReducer } from "react";
2
+ import { Header } from "./components/header";
3
+ import { Main } from "./components/main";
4
+ import { Footer } from "./components/footer";
5
+
6
+ import { todoReducer } from "./reducer";
7
+
8
+ import "./app.css";
9
+
10
+ export function App() {
11
+ const [todos, dispatch] = useReducer(todoReducer, []);
12
+
13
+ return (
14
+ <>
15
+ <Header dispatch={dispatch} />
16
+ <Main todos={todos} dispatch={dispatch} />
17
+ <Footer todos={todos} dispatch={dispatch} />
18
+ </>
19
+ );
20
+ }
@@ -0,0 +1,46 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+ import classnames from "classnames";
4
+
5
+ import { REMOVE_COMPLETED_ITEMS } from "../constants";
6
+
7
+ export function Footer({ todos, dispatch }) {
8
+ const { pathname: route } = useLocation();
9
+
10
+ const activeTodos = useMemo(() => todos.filter((todo) => !todo.completed), [todos]);
11
+
12
+ const removeCompleted = useCallback(() => dispatch({ type: REMOVE_COMPLETED_ITEMS }), [dispatch]);
13
+
14
+ // prettier-ignore
15
+ if (todos.length === 0)
16
+ return null;
17
+
18
+ return (
19
+ <div className="footer" data-testid="footer">
20
+ <div className="todo-count">{`${activeTodos.length} ${activeTodos.length === 1 ? "item" : "items"} left!`}</div>
21
+ <div className="filters" data-testid="footer-navigation">
22
+ <div className="filter-item">
23
+ <div className={classnames("filter-link", { selected: route === "/" })} onClick={() => window.location.hash = "/"}>
24
+ All
25
+ </div>
26
+ </div>
27
+ <div className="filter-item">
28
+ <div className={classnames("filter-link", { selected: route === "/active" })} onClick={() => window.location.hash = "/active"}>
29
+ Active
30
+ </div>
31
+ </div>
32
+ <div className="filter-item">
33
+ <div className={classnames("filter-link", { selected: route === "/completed" })} onClick={() => window.location.hash = "/completed"}>
34
+ Completed
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <div
39
+ className={classnames("clear-completed", { disabled: activeTodos.length === todos.length })}
40
+ onClick={activeTodos.length === todos.length ? null : removeCompleted}
41
+ >
42
+ Clear completed
43
+ </div>
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,15 @@
1
+ import { useCallback } from "react";
2
+ import { Input } from "./input";
3
+
4
+ import { ADD_ITEM } from "../constants";
5
+
6
+ export function Header({ dispatch }) {
7
+ const addItem = useCallback((title) => dispatch({ type: ADD_ITEM, payload: { title } }), [dispatch]);
8
+
9
+ return (
10
+ <div className="header" data-testid="header">
11
+ <div className="header-title">todos</div>
12
+ <Input onSubmit={addItem} label="New Todo Input" placeholder="What needs to be done?" />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,46 @@
1
+ import { useCallback } from "react";
2
+
3
+ const sanitize = (string) => {
4
+ const map = {
5
+ "&": "&amp;",
6
+ "<": "&lt;",
7
+ ">": "&gt;",
8
+ '"': "&quot;",
9
+ "'": "&#x27;",
10
+ "/": "&#x2F;",
11
+ };
12
+ const reg = /[&<>"'/]/gi;
13
+ return string.replace(reg, (match) => map[match]);
14
+ };
15
+
16
+ const hasValidMin = (value, min) => {
17
+ return value.length >= min;
18
+ };
19
+
20
+ export function Input({ onSubmit, placeholder, label, defaultValue, onBlur }) {
21
+ const handleBlur = useCallback(() => {
22
+ if (onBlur)
23
+ onBlur();
24
+ }, [onBlur]);
25
+
26
+ const handleKeyDown = useCallback(
27
+ (e) => {
28
+ if (e.key === "Enter") {
29
+ const value = e.target.value.trim();
30
+
31
+ if (!hasValidMin(value, 2))
32
+ return;
33
+
34
+ onSubmit(sanitize(value));
35
+ e.target.value = "";
36
+ }
37
+ },
38
+ [onSubmit]
39
+ );
40
+
41
+ return (
42
+ <div className="input-container">
43
+ <input className="new-todo" id="todo-input" type="text" data-testid="text-input" autoFocus placeholder={placeholder} defaultValue={defaultValue} onBlur={handleBlur} onKeyDown={handleKeyDown} />
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,55 @@
1
+ import { memo, useState, useCallback } from "react";
2
+ import classnames from "classnames";
3
+
4
+ import { Input } from "./input";
5
+
6
+ import { TOGGLE_ITEM, REMOVE_ITEM, UPDATE_ITEM } from "../constants";
7
+
8
+ export const Item = memo(function Item({ todo, dispatch, index }) {
9
+ const [isWritable, setIsWritable] = useState(false);
10
+ const { title, completed, id } = todo;
11
+
12
+ const toggleItem = useCallback(() => dispatch({ type: TOGGLE_ITEM, payload: { id } }), [dispatch]);
13
+ const removeItem = useCallback(() => dispatch({ type: REMOVE_ITEM, payload: { id } }), [dispatch]);
14
+ const updateItem = useCallback((id, title) => dispatch({ type: UPDATE_ITEM, payload: { id, title } }), [dispatch]);
15
+
16
+ const handleDoubleClick = useCallback(() => {
17
+ setIsWritable(true);
18
+ }, []);
19
+
20
+ const handleBlur = useCallback(() => {
21
+ setIsWritable(false);
22
+ }, []);
23
+
24
+ const handleUpdate = useCallback(
25
+ (title) => {
26
+ if (title.length === 0)
27
+ removeItem(id);
28
+ else
29
+ updateItem(id, title);
30
+
31
+ setIsWritable(false);
32
+ },
33
+ [id, removeItem, updateItem]
34
+ );
35
+
36
+ return (
37
+ <div className={classnames("todo-item", { completed: todo.completed })} data-testid="todo-item">
38
+ <div className="view">
39
+ {isWritable ? (
40
+ <Input onSubmit={handleUpdate} label="Edit Todo Input" defaultValue={title} onBlur={handleBlur} />
41
+ ) : (
42
+ <>
43
+ <div className="toggle checkbox-wrapper" data-testid="todo-item-toggle">
44
+ <div className={classnames("checkbox", { checked: completed })} onClick={toggleItem}></div>
45
+ </div>
46
+ <div className="todo-text" data-testid="todo-item-label" onDoubleClick={handleDoubleClick}>
47
+ {title}
48
+ </div>
49
+ <div className="destroy" data-testid="todo-item-button" onClick={removeItem} role="button"></div>
50
+ </>
51
+ )}
52
+ </div>
53
+ </div>
54
+ );
55
+ });
@@ -0,0 +1,45 @@
1
+ import { useMemo, useCallback } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+
4
+ import { Item } from "./item";
5
+ import classnames from "classnames";
6
+
7
+ import { TOGGLE_ALL } from "../constants";
8
+
9
+ export function Main({ todos, dispatch }) {
10
+ const { pathname: route } = useLocation();
11
+
12
+ const visibleTodos = useMemo(
13
+ () =>
14
+ todos.filter((todo) => {
15
+ if (route === "/active")
16
+ return !todo.completed;
17
+
18
+ if (route === "/completed")
19
+ return todo.completed;
20
+
21
+ return todo;
22
+ }),
23
+ [todos, route]
24
+ );
25
+
26
+ const toggleAll = useCallback((e) => dispatch({ type: TOGGLE_ALL, payload: { completed: e.target.checked } }), [dispatch]);
27
+
28
+ return (
29
+ <main className="main" data-testid="main">
30
+ {visibleTodos.length > 0 ? (
31
+ <div className="toggle-all-container">
32
+ <input className="toggle-all" type="checkbox" id="toggle-all" data-testid="toggle-all" checked={visibleTodos.every((todo) => todo.completed)} onChange={toggleAll} />
33
+ <label className="toggle-all-label" htmlFor="toggle-all">
34
+ Toggle All Input
35
+ </label>
36
+ </div>
37
+ ) : null}
38
+ <div className={classnames("todo-list")} data-testid="todo-list">
39
+ {visibleTodos.map((todo, index) => (
40
+ <Item todo={todo} key={todo.id} dispatch={dispatch} index={index} />
41
+ ))}
42
+ </div>
43
+ </main>
44
+ );
45
+ }
@@ -0,0 +1,7 @@
1
+ export const ADD_ITEM = "ADD_ITEM";
2
+ export const UPDATE_ITEM = "UPDATE_ITEM";
3
+ export const REMOVE_ITEM = "REMOVE_ITEM";
4
+ export const TOGGLE_ITEM = "TOGGLE_ITEM";
5
+ export const REMOVE_ALL_ITEMS = "REMOVE_ALL_ITEMS";
6
+ export const TOGGLE_ALL = "TOGGLE_ALL";
7
+ export const REMOVE_COMPLETED_ITEMS = "REMOVE_COMPLETED_ITEMS";
@@ -0,0 +1,64 @@
1
+ import { ADD_ITEM, UPDATE_ITEM, REMOVE_ITEM, TOGGLE_ITEM, REMOVE_ALL_ITEMS, TOGGLE_ALL, REMOVE_COMPLETED_ITEMS } from "./constants";
2
+
3
+ /* Borrowed from https://github.com/ai/nanoid/blob/3.0.2/non-secure/index.js
4
+
5
+ The MIT License (MIT)
6
+
7
+ Copyright 2017 Andrey Sitnik <andrey@sitnik.ru>
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
10
+ this software and associated documentation files (the "Software"), to deal in
11
+ the Software without restriction, including without limitation the rights to
12
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
13
+ the Software, and to permit persons to whom the Software is furnished to do so,
14
+ subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
21
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
22
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
23
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
25
+
26
+ // This alphabet uses `A-Za-z0-9_-` symbols.
27
+ // The order of characters is optimized for better gzip and brotli compression.
28
+ // References to the same file (works both for gzip and brotli):
29
+ // `'use`, `andom`, and `rict'`
30
+ // References to the brotli default dictionary:
31
+ // `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf`
32
+ let urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
33
+
34
+ function nanoid(size = 21) {
35
+ let id = "";
36
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
37
+ let i = size;
38
+ while (i--) {
39
+ // `| 0` is more compact and faster than `Math.floor()`.
40
+ id += urlAlphabet[(Math.random() * 64) | 0];
41
+ }
42
+ return id;
43
+ }
44
+
45
+ export const todoReducer = (state, action) => {
46
+ switch (action.type) {
47
+ case ADD_ITEM:
48
+ return state.concat({ id: nanoid(), title: action.payload.title, completed: false });
49
+ case UPDATE_ITEM:
50
+ return state.map((todo) => (todo.id === action.payload.id ? { ...todo, title: action.payload.title } : todo));
51
+ case REMOVE_ITEM:
52
+ return state.filter((todo) => todo.id !== action.payload.id);
53
+ case TOGGLE_ITEM:
54
+ return state.map((todo) => (todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo));
55
+ case REMOVE_ALL_ITEMS:
56
+ return [];
57
+ case TOGGLE_ALL:
58
+ return state.map((todo) => (todo.completed !== action.payload.completed ? { ...todo, completed: action.payload.completed } : todo));
59
+ case REMOVE_COMPLETED_ITEMS:
60
+ return state.filter((todo) => !todo.completed);
61
+ }
62
+
63
+ throw Error(`Unknown action: ${action.type}`);
64
+ };
@@ -0,0 +1,43 @@
1
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
2
+ const path = require("path");
3
+
4
+ module.exports = {
5
+ entry: {
6
+ app: path.resolve(__dirname, "src", "index.js"),
7
+ },
8
+ plugins: [
9
+ new HtmlWebpackPlugin({
10
+ title: "TodoMVC: React",
11
+ template: path.resolve(__dirname, "public", "index.html"),
12
+ }),
13
+ ],
14
+ output: {
15
+ filename: "[name].bundle.js",
16
+ path: path.resolve(__dirname, "dist"),
17
+ clean: true,
18
+ },
19
+ resolve: {
20
+ extensions: [".js", ".jsx"],
21
+ },
22
+ module: {
23
+ rules: [
24
+ {
25
+ test: /\.(js|jsx)$/,
26
+ exclude: /node_modules/,
27
+ use: {
28
+ loader: "babel-loader",
29
+ options: {
30
+ presets: [
31
+ ["@babel/preset-env", { targets: "defaults" }],
32
+ ["@babel/preset-react", { runtime: "automatic" }],
33
+ ],
34
+ },
35
+ },
36
+ },
37
+ {
38
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
39
+ type: "asset/resource",
40
+ },
41
+ ],
42
+ },
43
+ };
@@ -0,0 +1,18 @@
1
+ const { merge } = require("webpack-merge");
2
+ const common = require("./webpack.common.js");
3
+
4
+ module.exports = merge(common, {
5
+ mode: "development",
6
+ devtool: "inline-source-map",
7
+ devServer: {
8
+ static: "./dist",
9
+ },
10
+ module: {
11
+ rules: [
12
+ {
13
+ test: /\.css$/i,
14
+ use: ["style-loader", "css-loader"],
15
+ },
16
+ ],
17
+ },
18
+ });
@@ -0,0 +1,35 @@
1
+ const { merge } = require("webpack-merge");
2
+ const common = require("./webpack.common.js");
3
+
4
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5
+ const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
6
+ const TerserPlugin = require("terser-webpack-plugin");
7
+ const CopyPlugin = require("copy-webpack-plugin");
8
+
9
+ module.exports = merge(common, {
10
+ mode: "production",
11
+ devtool: "source-map",
12
+ plugins: [
13
+ new MiniCssExtractPlugin({
14
+ filename: "[name].css",
15
+ chunkFilename: "[id].css",
16
+ }),
17
+ new CopyPlugin({
18
+ patterns: [
19
+ { from: "./node_modules/todomvc-common/base.js", to: "base.js" },
20
+ ],
21
+ }),
22
+ ],
23
+ module: {
24
+ rules: [
25
+ {
26
+ test: /\.css$/,
27
+ use: [MiniCssExtractPlugin.loader, "css-loader"],
28
+ },
29
+ ],
30
+ },
31
+ optimization: {
32
+ minimize: true,
33
+ minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
34
+ },
35
+ });