Commit d7a0b7d8 authored by Yannick Li's avatar Yannick Li
Browse files

Prettier: prettify codebase

parent 3cf4d0c7
module.exports = {
"root": true,
"env": {
"browser": true,
"es2021": true
root: true,
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"react-app",
"react-app/jest",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"react-app",
"react-app/jest"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {},
};
......@@ -12,7 +12,7 @@ For the next steps, you will need the following software:
- Make sure you have the latest version of Node.js (except Node v15): [see official installation guide](https://nodejs.org/en/download/);
1.**Install Project dependencies**
1.**Install Project dependencies**
Go to project root directory and:
......
......@@ -4,10 +4,7 @@
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Concordant Sudoku"
/>
<meta name="description" content="Concordant Sudoku" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
......@@ -23,8 +20,11 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap"
rel="stylesheet"
/>
<title>C-Sudoku</title>
</head>
<body>
......
......@@ -23,15 +23,15 @@
*/
import React from "react";
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { mount } from 'enzyme';
import Cell from "./Cell"
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { mount } from "enzyme";
import Cell from "./Cell";
configure({ adapter: new Adapter() });
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => ({}));
jest.spyOn(console, "log").mockImplementation(() => ({}));
});
describe("Testing UI", () => {
......@@ -40,13 +40,10 @@ describe("Testing UI", () => {
* Here it should display 6.
*/
test("Cell value initialization", () => {
const wrapper = mount(<Cell
index={3}
value={"6"}
onChange={() => ({})}
lock={false}
/>)
expect(wrapper.find('textarea').text()).toBe("6")
const wrapper = mount(
<Cell index={3} value={"6"} onChange={() => ({})} lock={false} />
);
expect(wrapper.find("textarea").text()).toBe("6");
});
/**
......@@ -55,15 +52,12 @@ describe("Testing UI", () => {
* Here it should display 2.
*/
test("Cell value changed", () => {
const wrapper = mount(<Cell
index={3}
value={"6"}
onChange={() => ({})}
lock={false}
/>)
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "2"} })
expect(wrapper.find('textarea').text()).toBe("2")
const wrapper = mount(
<Cell index={3} value={"6"} onChange={() => ({})} lock={false} />
);
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "2" } });
expect(wrapper.find("textarea").text()).toBe("2");
});
/**
......@@ -71,35 +65,29 @@ describe("Testing UI", () => {
* Here it should display 2 even if we try to modify it.
*/
test("Cell locked", () => {
const wrapper = mount(<Cell
index={3}
value={"6"}
onChange={() => ({})}
lock={true}
/>)
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "2"} })
expect(wrapper.find('textarea').text()).toBe("6")
const wrapper = mount(
<Cell index={3} value={"6"} onChange={() => ({})} lock={true} />
);
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "2" } });
expect(wrapper.find("textarea").text()).toBe("6");
});
/**
* This test evaluates that the value of a cell can only be modified with integers from 1 to 9.
*/
test("Cell wrong value", () => {
const wrapper = mount(<Cell
index={3}
value={"6"}
onChange={() => ({})}
lock={false}
/>)
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "0"} })
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "10"} })
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "1.1"} })
expect(wrapper.find('textarea').text()).toBe("6")
wrapper.find('textarea').simulate('change', { target: {value: "abc"} })
expect(wrapper.find('textarea').text()).toBe("6")
const wrapper = mount(
<Cell index={3} value={"6"} onChange={() => ({})} lock={false} />
);
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "0" } });
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "10" } });
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "1.1" } });
expect(wrapper.find("textarea").text()).toBe("6");
wrapper.find("textarea").simulate("change", { target: { value: "abc" } });
expect(wrapper.find("textarea").text()).toBe("6");
});
});
......@@ -22,8 +22,8 @@
* SOFTWARE.
*/
import React from 'react';
import { validInput } from '../constants'
import React from "react";
import { validInput } from "../constants";
/**
* Interface for the properties of the Cell.
......@@ -31,53 +31,55 @@ import { validInput } from '../constants'
* used when the value is modified and if we are allowed to modify the value.
*/
interface ICellProps {
index: number,
value: string,
onChange: ((index: number, value: string) => void) | null,
error: boolean
index: number;
value: string;
onChange: ((index: number, value: string) => void) | null;
error: boolean;
}
/**
* This class represent a cell of the Sudoku
*/
class Cell extends React.Component<ICellProps> {
/**
* onChange event handler
* @param event handled
*/
onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (this.props.onChange === null) {
return
}
if (event.target.value === "" || validInput.test(event.target.value)) {
this.props.onChange(this.props.index, event.target.value)
} else {
console.error("Invalid input in cell " + this.props.index + " : " + event.target.value)
}
/**
* onChange event handler
* @param event handled
*/
onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (this.props.onChange === null) {
return;
}
if (event.target.value === "" || validInput.test(event.target.value)) {
this.props.onChange(this.props.index, event.target.value);
} else {
console.error(
"Invalid input in cell " + this.props.index + " : " + event.target.value
);
}
}
render(): JSX.Element {
let cellClass = ""
if (this.props.value.length > 1) {
cellClass += "mv "
}
if (this.props.onChange === null) {
cellClass += "locked "
}
if (this.props.error){
cellClass += "wrongvalue "
}
return (
<input
id={String(this.props.index)}
className={cellClass}
maxLength={1}
value={this.props.value}
onChange={(event) => this.onChange(event)}
readOnly={this.props.onChange === null}
/>
);
render(): JSX.Element {
let cellClass = "";
if (this.props.value.length > 1) {
cellClass += "mv ";
}
if (this.props.onChange === null) {
cellClass += "locked ";
}
if (this.props.error) {
cellClass += "wrongvalue ";
}
return (
<input
id={String(this.props.index)}
className={cellClass}
maxLength={1}
value={this.props.value}
onChange={(event) => this.onChange(event)}
readOnly={this.props.onChange === null}
/>
);
}
}
export default Cell
export default Cell;
......@@ -22,18 +22,16 @@
* SOFTWARE.
*/
import React from 'react';
import Grid from './Grid';
import React from "react";
import Grid from "./Grid";
/**
* This class represent the Game that glues all components together.
*/
class Game extends React.Component {
render(): JSX.Element {
return (
<Grid />
);
}
render(): JSX.Element {
return <Grid />;
}
}
export default Game
export default Game;
This diff is collapsed.
......@@ -22,290 +22,315 @@
* SOFTWARE.
*/
import React from 'react';
import assert from 'assert';
import Cell from './Cell';
import { validInput, GRIDS } from '../constants'
import React from "react";
import assert from "assert";
import Cell from "./Cell";
import { validInput, GRIDS } from "../constants";
/**
* Interface for the state of the Grid
*/
interface IGridState {
cells: {value: string, modifiable: boolean, error: boolean}[],
isFinished: boolean
cells: { value: string; modifiable: boolean; error: boolean }[];
isFinished: boolean;
}
/**
* This class represent the grid of the Sudoku
*/
class Grid extends React.Component<Record<string, unknown>, IGridState> {
constructor(props: Record<string, unknown>) {
super(props);
const cells = new Array(81).fill(null).map(()=>({value:"", modifiable:false, error:false}));
this.state = {
cells: cells,
isFinished: false
};
}
constructor(props: Record<string, unknown>) {
super(props);
const cells = new Array(81)
.fill(null)
.map(() => ({ value: "", modifiable: false, error: false }));
this.state = {
cells: cells,
isFinished: false,
};
}
/**
* Called after the component is rendered.
* It set a timer to refresh cells values.
*/
componentDidMount(): void {
this.initFrom(generateStaticGrid());
}
/**
* Called after the component is rendered.
* It set a timer to refresh cells values.
*/
componentDidMount(): void {
this.initFrom(generateStaticGrid());
/**
* Initialize the grid with the given values.
* @param values values to be set in the grid.
*/
initFrom(values: string): void {
assert.ok(values.length === 81);
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
cells[index].value = values[index] === "." ? "" : values[index];
cells[index].modifiable = values[index] === "." ? true : false;
}
this.updateState(cells);
}
/**
* Initialize the grid with the given values.
* @param values values to be set in the grid.
*/
initFrom(values: string): void {
assert.ok(values.length === 81);
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
cells[index].value = values[index] === "." ? "" : values[index];
cells[index].modifiable = values[index] === "." ? true : false;
}
this.updateState(cells)
/**
* Reset the value of all modifiable cells.
*/
reset(): void {
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
if (cells[index].modifiable) {
cells[index].value = "";
}
}
this.updateState(cells);
}
/**
* Reset the value of all modifiable cells.
*/
reset(): void {
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
if (cells[index].modifiable) {
cells[index].value = "";
}
}
this.updateState(cells)
/**
* This handler is called when the value of a cell is changed.
* @param index The index of the cell changed.
* @param value The new value of the cell.
*/
handleChange(index: number, value: string): void {
assert.ok(value === "" || (Number(value) >= 1 && Number(value) <= 9));
assert.ok(index >= 0 && index < 81);
if (!this.state.cells[index].modifiable) {
console.error(
"Trying to change an non modifiable cell. Should not happend"
);
}
/**
* This handler is called when the value of a cell is changed.
* @param index The index of the cell changed.
* @param value The new value of the cell.
*/
handleChange(index: number, value: string): void {
assert.ok(value === "" || (Number(value) >= 1 && Number(value) <= 9))
assert.ok(index >= 0 && index < 81)
if (!this.state.cells[index].modifiable) {
console.error("Trying to change an non modifiable cell. Should not happend");
const cells = this.state.cells;
cells[index].value = value;
this.updateState(cells);
}
/**
* This function return a React element corresponding to a cell.
* @param index The index of the cell to render.
*/
renderCell(index: number): JSX.Element {
assert.ok(index >= 0 && index < 81);
return (
<Cell
index={index}
value={this.state.cells[index].value}
onChange={
this.state.cells[index].modifiable
? (index: number, value: string) => this.handleChange(index, value)
: null
}
error={this.state.cells[index].error}
/>
);
}
const cells = this.state.cells;
cells[index].value = value;
this.updateState(cells);
}
/**
* This function return a React element corresponding to a block of cell.
* @param blockNum The index of the block to render.
*/
renderBlock(blockNum: number): JSX.Element {
assert.ok(blockNum >= 0 && blockNum < 9);
const index = blockIndex(blockNum);
return (
<td>
{this.renderCell(index[0])}
{this.renderCell(index[1])}
{this.renderCell(index[2])}
<br />
{this.renderCell(index[3])}
{this.renderCell(index[4])}
{this.renderCell(index[5])}
<br />
{this.renderCell(index[6])}
{this.renderCell(index[7])}
{this.renderCell(index[8])}
</td>
);
}
/**
* This function return a React element corresponding to a cell.
* @param index The index of the cell to render.
*/
renderCell(index: number): JSX.Element {
assert.ok(index >= 0 && index < 81)
return (
<Cell
index={index}
value={this.state.cells[index].value}
onChange={this.state.cells[index].modifiable ? (index:number, value:string) => this.handleChange(index, value) : null}
error={this.state.cells[index].error}
/>
);
/**
* The function is called when the grid is updated. It return a React element corresponding to the grid of the Sudoku.
*/
render(): JSX.Element {
return (
<div className="sudoku">
<div>
<button onClick={this.reset.bind(this)}>Reset</button>
</div>
<br />
<table className="grid">
<tbody>
{[0, 1, 2].map((line) => (
<tr key={line.toString()}>
{this.renderBlock(line * 3)}
{this.renderBlock(line * 3 + 1)}
{this.renderBlock(line * 3 + 2)}
</tr>
))}
</tbody>
</table>
{this.state.isFinished && (
<h2 className="status" id="status">
Sudoku completed
</h2>
)}
</div>
);
}
/**
* Check if a line respect Sudoku lines rules.
* @param line The line number to be checked.
*/
checkLine(line: number): boolean {
assert.ok(line >= 0 && line < 9);
const cpt = Array(9).fill(0);
for (let column = 0; column < 9; column++) {
const index = line * 9 + column;
const val = this.state.cells[index].value;
if (val.length === 0 || val.length > 1) {
continue;
}