Skip to content

Commit

Permalink
completed React Typescript tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mangs committed Jan 5, 2024
1 parent 29cfdda commit af62d94
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## 1.2.0

- Add example code for each configuration to verify correctness during continuous integration
- Testing added using example code for each configuration to verify correctness during continuous integration
- React and Preact TypeScript configs now allow JSX in files with `.tsx` file extensions
- Remove dev dependency `npm-run-all` because it was only used during the `reinstall` NPM script and would cause an error if dependencies weren't installed prior to execution

## 1.1.0
Expand Down
4 changes: 3 additions & 1 deletion lib/eslintReactTypescriptConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
"browser": false,
"node": false
},
"rules": {}
"rules": {
"react/jsx-filename-extension": ["error", { "extensions": [".jsx", ".tsx"] }]
}
}
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
"@playwright/test": "1.40.1",
"@types/aws-lambda": "8.10.131",
"@types/node": "20.10.6",
"@types/react": "18.2.46",
"@types/react-dom": "18.2.18",
"eslint": "8.56.0",
"jest": "29.7.0",
"prettier": "3.1.0",
Expand Down
2 changes: 1 addition & 1 deletion test/eslintReactConfig/exampleReactConfigEntrypoint.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Code adapted from here: https://react.dev/learn/tutorial-tic-tac-toe#what-are-you-building
// Code based on this example: https://react.dev/learn/tutorial-tic-tac-toe#what-are-you-building

// External Imports
import { createRoot } from 'react-dom/client';
Expand Down
8 changes: 7 additions & 1 deletion test/eslintReactTypescriptConfig/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"root": true,
"extends": ["../../lib/eslintReactTypescriptConfig.json"]
"extends": ["../../lib/eslintReactTypescriptConfig.json", "../../lib/eslintBrowserConfig.json"],
"overrides": [
{
"files": "./components/*.tsx",
"rules": { "unicorn/filename-case": ["error", { "case": "pascalCase" }] }
}
]
}
72 changes: 72 additions & 0 deletions test/eslintReactTypescriptConfig/components/Board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// External Imports
import React from 'react';

// Internal Imports
import { Square } from './Square.tsx';

// Local Functions
function calculateWinner(squares: string[]) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (const [a, b, c] of lines) {
if (squares[a!] && squares[a!] === squares[b!] && squares[a!] === squares[c!]) {
return squares[a!];
}
}
// eslint-disable-next-line unicorn/no-null -- return an explicit null value
return null;
}

// Prop Types
interface BoardProps {
onPlay: (s: string[]) => void;
squares: string[];
xIsNext: boolean;
}

// Component Definition
function Board({ onPlay, squares, xIsNext }: BoardProps) {
function handleClick(index: number) {
if (calculateWinner(squares) ?? squares[index]) {
return;
}
const nextSquares = [...squares];
nextSquares[index] = xIsNext ? 'X' : 'O';
onPlay(nextSquares);
}

const winner = calculateWinner(squares);
const status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? 'X' : 'O'}`;

return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]!} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]!} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]!} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]!} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]!} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]!} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]!} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]!} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]!} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}

// Module Exports
export { Board };
53 changes: 53 additions & 0 deletions test/eslintReactTypescriptConfig/components/Game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// External Imports
import React, { useCallback, useState } from 'react';

// Internal Imports
import { Board } from './Board.tsx';

// Component Definition
function Game() {
// eslint-disable-next-line unicorn/no-null -- the game expects explicit null values
const [history, setHistory] = useState([Array.from({ length: 9 }).fill(null) as string[]]);
const [currentMove, setCurrentMove] = useState(0);
const handlePlay = useCallback(
(nextSquares: string[]) => {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
},
[currentMove, history],
);

function jumpTo(nextMove: number) {
setCurrentMove(nextMove);
}

const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove]!;

const moves = history.map((_squares, move) => {
const description = move > 0 ? `Go to move #${move}` : 'Go to game start';
return (
// eslint-disable-next-line react/no-array-index-key -- this is the best unique value
<li key={move}>
<button type="button" onClick={() => jumpTo(move)}>
{description}
</button>
</li>
);
});

return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}

// Module Exports
export { Game };
20 changes: 20 additions & 0 deletions test/eslintReactTypescriptConfig/components/Square.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// External Imports
import React from 'react';

// Prop Types
interface SquareProps {
onSquareClick: () => void;
value: string;
}

// Component Definition
function Square({ onSquareClick, value }: SquareProps) {
return (
<button type="button" className="square" onClick={onSquareClick}>
{value}
</button>
);
}

// Module Export
export { Square };
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Code based on this example: https://react.dev/learn/tutorial-tic-tac-toe#what-are-you-building

// External Imports
import { createRoot } from 'react-dom/client';
import React from 'react';

// Internal Imports
import { Game } from './components/Game.tsx';

// Render the Application
const rootElement = document.querySelector('#react-root');
if (!rootElement) {
throw new TypeError('React root element not found');
}
const reactRoot = createRoot(rootElement);
reactRoot.render(<Game />);
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"jsx": "preserve",

// ASTRO BASE TSCONFIG
// Enable top-level await, and other modern ESM features.
Expand Down

0 comments on commit af62d94

Please sign in to comment.