diff --git a/collaborative/src/components/Cell.test.tsx b/collaborative/src/components/Cell.test.tsx index 2b043e74a694e5459c20f21bbadd631fb493abe0..2ea8d7dd30ac35e10326ff152c40f9fc3e0c4e79 100644 --- a/collaborative/src/components/Cell.test.tsx +++ b/collaborative/src/components/Cell.test.tsx @@ -22,7 +22,6 @@ * SOFTWARE. */ -import React from "react"; import { configure } from "enzyme"; import Adapter from "enzyme-adapter-react-16"; import { mount } from "enzyme"; @@ -36,58 +35,14 @@ beforeEach(() => { describe("Testing UI", () => { /** - * This test evaluates that the cell displays the initial value. + * This test evaluates that the cell displays the value. * Here it should display 6. */ - test("Cell value initialization", () => { + test("Cell value", () => { const wrapper = mount( - <Cell index={3} value={"6"} onChange={() => ({})} lock={false} /> + <Cell index={3} value={"6"} onChange={() => ({})} error={false} /> ); - expect(wrapper.find("textarea").text()).toBe("6"); - }); - - /** - * This test evaluates that after updating the cell value, - * the cell display the right value. - * 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"); - }); - - /** - * This test evaluates that the value of a locked cell cannot be modified. - * 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"); - }); - - /** - * 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 input = wrapper.find('input'); + expect(input.instance().value).toBe("6"); }); }); diff --git a/collaborative/src/components/Cell.tsx b/collaborative/src/components/Cell.tsx index da8a936bca4732a452d5ace8a7ad0abc9e9bfb64..567a9cc120235b44f9849e87c74da535b853ed37 100644 --- a/collaborative/src/components/Cell.tsx +++ b/collaborative/src/components/Cell.tsx @@ -51,10 +51,6 @@ class Cell extends React.Component<ICellProps> { } 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 - ); } } diff --git a/collaborative/src/components/Game.test.tsx b/collaborative/src/components/Game.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4253d0431bd19066a33e4a499fd7eb0fd041af88 --- /dev/null +++ b/collaborative/src/components/Game.test.tsx @@ -0,0 +1,551 @@ +/** + * MIT License + * + * Copyright (c) 2020, Concordant and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from "react"; +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { shallow, mount } from "enzyme"; +import Game from "./Game"; +import { crdtlib } from "@concordant/c-crdtlib"; + +configure({ adapter: new Adapter() }); + +beforeEach(() => { + jest.spyOn(console, "log").mockImplementation(() => ({})); +}); + +/** + * This test evaluates that checkLine return true only if + * the array contains all numbers from 1 to 9 without repeat. + */ + describe("checkLine", () => { + test("checkLine", () => { + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLine(3)).toBe(true); + game.handleChange(35, "1") + expect(game.checkLine(3)).toBe(false); + game.handleChange(35, "2") + expect(game.checkLine(3)).toBe(true); + }); +}); + +/** + * This test evaluates that checkColumn return true only if + * the array contains all numbers from 1 to 9 without repeat. + */ +describe("checkColumn", () => { + test("checkColumn", () => { + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumn(8)).toBe(true); + game.handleChange(35, "1") + expect(game.checkColumn(8)).toBe(false); + game.handleChange(35, "2") + expect(game.checkColumn(8)).toBe(true); + }); +}); + +/** + * This test evaluates that checkBlock return true only if + * the array contains all numbers from 1 to 9 without repeat. + */ +describe("checkBlock", () => { + test("checkBlock", () => { + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlock(5)).toBe(true); + game.handleChange(35, "1") + expect(game.checkBlock(5)).toBe(false); + game.handleChange(35, "2") + expect(game.checkBlock(5)).toBe(true); + }); +}); + +/** + * This test evaluates that validateLine return false if a line contains an error. + */ +describe("validateLine", () => { + /** + * This test evaluates that validateLine return true with a line containing full empty cell. + */ + test("validateLine empty", () => { + // prettier-ignore + const initVal = ["","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","",""] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLines()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkLines()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateLine return true with a line containing all numbers + * from 1 to 9 without repeat. + */ + test("validateLine complete", () => { + // prettier-ignore + const initVal = ["6","5","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLines()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkLines()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateLine return true if a line is missing a value. + */ + test("validateLine with missing values", () => { + // prettier-ignore + const initVal = ["6","","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLines()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkLines()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateLine return false when a value is dupplicated. + */ + test("validateLine with wrongs values", () => { + // prettier-ignore + const initVal = ["6","2","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLines()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkLines()).toStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]); + }); + + /** + * This test evaluates that validateLine return true when a cell has multiple values. + */ + test("validateLine with multiples values", () => { + // prettier-ignore + const initVal = ["1 6","5","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkLines()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkLines()).toStrictEqual([]); + }); +}); + +/** + * This test evaluates that validateColumn return false if a column contains an error. + */ +describe("validateColumn", () => { + /** + * This test evaluates that validateColumn return true with a column containing full empty cell. + */ + test("validateColumn empty", () => { + // prettier-ignore + const initVal = ["","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","",""] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumns()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkColumns()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateColumn return true with a column containing all numbers + * from 1 to 9 without repeat. + */ + test("validateColumn complete", () => { + // prettier-ignore + const initVal = ["6","5","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumns()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkColumns()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateColumn return true if a column is missing a value. + */ + test("validateColumn with missing values", () => { + // prettier-ignore + const initVal = ["6","","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumns()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkColumns()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateColumn return false when a value is dupplicated. + */ + test("validateColumn with wrongs values", () => { + // prettier-ignore + const initVal = ["6","2","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumns()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkColumns()).toStrictEqual([1, 10, 19, 28, 37, 46, 55, 64, 73]); + }); + + /** + * This test evaluates that validateColumn return true when a cell has multiple values. + */ + test("validateColumn with multiples values", () => { + // prettier-ignore + const initVal = ["1 6","5","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkColumns()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkColumns()).toStrictEqual([]); + }); +}); + +/** + * This test evaluates that validateBlock return false if a line contains an error. + */ +describe("validateBlock", () => { + /** + * This test evaluates that validateBlock return true with a line containing full empty cell. + */ + test("validateBlock empty", () => { + // prettier-ignore + const initVal = ["","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","",""] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlocks()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkBlocks()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateBlock return true with a line containing all numbers + * from 1 to 9 without repeat. + */ + test("validateBlock complete", () => { + // prettier-ignore + const initVal = ["6","5","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlocks()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkBlocks()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateBlock return true if a line is missing a value. + */ + test("validateBlock with missing values", () => { + // prettier-ignore + const initVal = ["6","","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlocks()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkBlocks()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateBlock return false when a value is dupplicated. + */ + test("validateBlock with wrongs values", () => { + // prettier-ignore + const initVal = ["6","2","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlocks()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkBlocks()).toStrictEqual([0, 1, 2, 9, 10, 11, 18, 19, 20]); + }); + + /** + * This test evaluates that validateBlock return true when a cell has multiple values. + */ + test("validateBlock with multiples values", () => { + // prettier-ignore + const initVal = ["1 6","5","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkBlocks()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkBlocks()).toStrictEqual([]); + }); +}); + +/** + * This test evaluates that validateSudoku return false if a line, column or block contains an error. + */ +describe("validateSudoku", () => { + /** + * This test evaluates that validateSudoku return true with a full empty game. + */ + test("validateSudoku empty", () => { + // prettier-ignore + const initVal = ["","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","","", + "","","","","","","","",""] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkCells()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkCells()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateSudoku return true with all lines, columns and blocks + * containing all numbers from 1 to 9 without repeat. + */ + test("validateSudoku complete", () => { + // prettier-ignore + const initVal = ["6","5","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkCells()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkCells()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateSudoku return true if the grid is missing a value. + */ + test("validateSudoku with missing values", () => { + // prettier-ignore + const initVal = ["6","","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkCells()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkCells()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateSudoku return false when a value is dupplicated. + */ + test("validateSudoku with wrongs values", () => { + // prettier-ignore + const initVal = ["6","2","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkCells()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkCells()).toStrictEqual([]); + }); + + /** + * This test evaluates that validateSudoku return true when a cell has multiple values. + */ + test("validateSudoku with multiples values", () => { + // prettier-ignore + const initVal = ["1 6","5 2","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = shallow(<Game />); + const game = wrapper.instance(); + + expect(game.checkCells()).toStrictEqual([]); + game.updateState(initVal.map(val =>({ value: val, modifiable: false, error: false }))) + expect(game.checkCells()).toStrictEqual([0, 1]); + }); +}); diff --git a/collaborative/src/components/Game.tsx b/collaborative/src/components/Game.tsx index 52dc1d5ae0eeed98b032608a4f2fa40e4cb69b2d..1f90cba7e1b8c2e945275e3fc459bb67a8075bc0 100644 --- a/collaborative/src/components/Game.tsx +++ b/collaborative/src/components/Game.tsx @@ -23,43 +23,419 @@ */ import React from "react"; -import Grid from "./Grid"; +import assert from "assert"; +import Grid, { cellsIndexOfBlock } from "./Grid"; +import { validInput, GRIDS } from "../constants"; import { client } from "@concordant/c-client"; +import Submit1Input from "./Submit1Input"; import CONFIG from "../config.json"; +const session = client.Session.Companion.connect( + CONFIG.dbName, + CONFIG.serviceUrl, + CONFIG.credentials +); +const collection = session.openCollection("sudoku", false); + /** * Interface for the state of a Game. * Keep a reference to the opened session and opened MVMap. */ interface IGameState { - session: client.Session; - collection: client.Collection; + gridNum: string; + mvmap: any; // eslint-disable-line @typescript-eslint/no-explicit-any + cells: { value: string; modifiable: boolean; error: boolean }[]; + isConnected: boolean; + isFinished: boolean; } /** * This class represent the Game that glues all components together. */ class Game extends React.Component<Record<string, unknown>, IGameState> { + timerID!: NodeJS.Timeout; + modifiedCells: string[]; + constructor(props: Record<string, unknown>) { super(props); - const session = client.Session.Companion.connect( - CONFIG.dbName, - CONFIG.serviceUrl, - CONFIG.credentials + const cells = new Array(81) + .fill(null) + .map(() => ({ value: "", modifiable: false, error: false })); + this.modifiedCells = new Array(81).fill(null); + const gridNum = "1"; + const mvmap = collection.open( + "grid" + gridNum, + "MVMap", + false, + function () { + return; + } ); - const collection = session.openCollection("sudoku", false); this.state = { - session: session, - collection: collection, + gridNum: gridNum, + mvmap: mvmap, + cells: cells, + isConnected: true, + isFinished: false, }; } + /** + * Called after the component is rendered. + * It set a timer to refresh cells values. + */ + componentDidMount(): void { + this.initFrom(generateStaticGrid(this.state.gridNum)); + this.timerID = setInterval(() => this.updateGrid(), 1000); + } + + /** + * Called when the compenent is about to be removed from the DOM. + * It remove the timer set in componentDidMount(). + */ + componentWillUnmount(): void { + clearInterval(this.timerID); + } + + /** + * Update cells values from C-Client. + */ + updateGrid(): void { + const cells = this.state.cells; + if (!this.state.isConnected) { + console.error("updateGrid() called while not connected."); + return; + } + for (let index = 0; index < 81; index++) { + if (cells[index].modifiable) { + cells[index].value = ""; + } + } + session.transaction(client.utils.ConsistencyLevel.None, () => { + const itString = this.state.mvmap.iteratorString(); + while (itString.hasNext()) { + const val = itString.next(); + cells[val.first].value = hashSetToString(val.second); + } + }); + this.updateState(cells); + } + + /** + * This function is used to simulate the offline mode. + */ + switchConnection(): void { + if (this.state.isConnected) { + this.modifiedCells = new Array(81).fill(null); + clearInterval(this.timerID); + } else { + for (let index = 0; index < 81; index++) { + if ( + this.state.cells[index].modifiable && + this.modifiedCells[index] !== null + ) { + session.transaction( + client.utils.ConsistencyLevel.None, + () => { + this.state.mvmap.setString(index, this.modifiedCells[index]); + } + ); + } + } + this.timerID = setInterval(() => this.updateGrid(), 1000); + } + this.setState({ isConnected: !this.state.isConnected }); + } + + /** + * 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 = ""; + if (this.state.isConnected) { + session.transaction( + client.utils.ConsistencyLevel.None, + () => { + this.state.mvmap.setString(index, cells[index].value); + } + ); + } else { + this.modifiedCells[index] = ""; + } + } + } + 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" + ); + } + + const cells = this.state.cells; + cells[index].value = value; + this.updateState(cells); + + if (this.state.isConnected) { + session.transaction(client.utils.ConsistencyLevel.None, () => { + this.state.mvmap.setString(index, value); + }); + } else { + this.modifiedCells[index] = value; + } + } + + /** + * This handler is called when a new grid number is submit. + * @param gridNum Desired grid number. + */ + handleSubmit(gridNum: string): void { + if ( + Number(gridNum) < 1 || + Number(gridNum) > 100 || + gridNum === this.state.gridNum + ) { + return; + } + const mvmap = collection.open( + "grid" + gridNum, + "MVMap", + false, + function () { + return; + } + ); + this.setState({ gridNum: gridNum, mvmap: mvmap }); + this.initFrom(generateStaticGrid(gridNum)); + } + render(): JSX.Element { return ( - <Grid session={this.state.session} collection={this.state.collection} /> + <div className="sudoku"> + <div>Current grid : {this.state.gridNum}</div> + <Submit1Input + inputName="Grid" + onSubmit={this.handleSubmit.bind(this)} + /> + <div> + Difficulty levels: easy (1-20), medium (21-40), hard (41-60), + very-hard (61-80), insane (81-100) + </div> + <br /> + <div> + <button onClick={this.reset.bind(this)}>Reset</button> + </div> + <br /> + <div> + <button onClick={() => this.switchConnection()}> + {this.state.isConnected ? "Disconnect" : "Connect"} + </button> + </div> + <br /> + <Grid cells={this.state.cells} isFinished={this.state.isFinished} + onChange={(index: number, value: string) => this.handleChange(index, value)} /> + </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; + } + cpt[Number(val) - 1]++; + } + return cpt.every((c) => c <= 1); + } + + /** + * Check if a column respect Sudoku columns rules. + * @param column The column number to be checked. + */ + checkColumn(column: number): boolean { + assert.ok(column >= 0 && column < 9); + const cpt = Array(9).fill(0); + for (let line = 0; line < 9; line++) { + const index = line * 9 + column; + const val = this.state.cells[index].value; + if (val.length === 0 || val.length > 1) { + continue; + } + cpt[Number(val) - 1]++; + } + return cpt.every((c) => c <= 1); + } + + /** + * Check if a block respect Sudoku blocks rules. + * @param block The block number to be checked. + */ + checkBlock(block: number): boolean { + assert.ok(block >= 0 && block < 9); + const cpt = Array(9).fill(0); + const indexList = cellsIndexOfBlock(block); + for (const index of indexList) { + const val = this.state.cells[index].value; + if (val.length === 0 || val.length > 1) { + continue; + } + cpt[Number(val) - 1]++; + } + return cpt.every((c) => c <= 1); + } + + /** + * This function check if all lines respect Sudoku lines rules. + */ + checkLines(): number[] { + const indexList = []; + for (let line = 0; line < 9; line++) { + if (this.checkLine(line) === false) { + for (let column = 0; column < 9; column++) { + indexList.push(line * 9 + column); + } + } + } + return indexList; + } + + /** + * This function check if all columns respect Sudoku columns rules. + */ + checkColumns(): number[] { + const indexList = []; + for (let column = 0; column < 9; column++) { + if (this.checkColumn(column) === false) { + for (let line = 0; line < 9; line++) { + indexList.push(line * 9 + column); + } + } + } + return indexList; + } + + /** + * This function check if all blocks respect Sudoku blocks rules. + */ + checkBlocks(): number[] { + let indexList: number[] = []; + for (let block = 0; block < 9; block++) { + if (this.checkBlock(block) === false) { + indexList = indexList.concat(cellsIndexOfBlock(block)); + } + } + return indexList; + } + + /** + * This function check if cells contains multiple values. + */ + checkCells(): number[] { + const indexList = []; + for (let cell = 0; cell < 81; cell++) { + const val = this.state.cells[cell].value; + if (val.length > 1) { + indexList.push(cell); + } + } + return indexList; + } + + /** + * Check if all cells respect Sudoku rules and update cells. + * @param cells Current cells values. + */ + updateState( + cells: { value: string; modifiable: boolean; error: boolean }[] + ): void { + let errorIndexList = this.checkLines(); + errorIndexList = errorIndexList.concat(this.checkColumns()); + errorIndexList = errorIndexList.concat(this.checkBlocks()); + errorIndexList = errorIndexList.concat(this.checkCells()); + + const errorIndexSet = new Set(errorIndexList); + + for (let index = 0; index < 81; index++) { + if (errorIndexSet.has(index)) { + cells[index].error = true; + } else { + cells[index].error = false; + } + } + + if (errorIndexSet.size) { + this.setState({ cells: cells, isFinished: false }); + return; + } + + for (let index = 0; index < 81; index++) { + if (!validInput.test(this.state.cells[index].value)) { + this.setState({ cells: cells, isFinished: false }); + return; + } + } + this.setState({ cells: cells, isFinished: true }); + } +} + +/** + * Return a predefined Sudoku grid as a string. + * @param gridNum Desired grid number + */ + function generateStaticGrid(gridNum: string) { + return GRIDS[gridNum]; +} + +/** + * Concatenates all values of a HashSet as a String. + * @param set HashSet to be concatenated. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function hashSetToString(set: any): string { + const res = new Set(); + const it = set.iterator(); + while (it.hasNext()) { + const val = it.next(); + if (val !== "") { + res.add(val); + } + } + return Array.from(res).sort().join(" "); } export default Game; diff --git a/collaborative/src/components/Grid.test.tsx b/collaborative/src/components/Grid.test.tsx index 2d069c9aca025dc8635af48f59ce690074bacf99..68e8d4898e68d58cccbeaa1688e5f989a484d078 100644 --- a/collaborative/src/components/Grid.test.tsx +++ b/collaborative/src/components/Grid.test.tsx @@ -22,13 +22,10 @@ * SOFTWARE. */ -import React from "react"; import { configure } from "enzyme"; import Adapter from "enzyme-adapter-react-16"; -import { shallow, mount, render } from "enzyme"; -import Grid from "./Grid"; -import { checkArray, firstCellOfBlock, blockIndex } from "./Grid"; -import { crdtlib } from "@concordant/c-crdtlib"; +import { shallow, mount } from "enzyme"; +import Grid, { cellsIndexOfBlock } from "./Grid"; configure({ adapter: new Adapter() }); @@ -36,2934 +33,82 @@ beforeEach(() => { jest.spyOn(console, "log").mockImplementation(() => ({})); }); -/** - * This test evaluates that checkArray return true only if the array contains only integers equal to 0 or 1. - */ -describe("checkArray", () => { - test("checkArray", () => { - expect(checkArray([0, 1, 2, 3])).toBe(false); - expect(checkArray([0, 1, 0, 1])).toBe(true); - expect(checkArray([0, -1, 0, 1])).toBe(false); - }); -}); - -/** - * This test evaluates that firstCellOfBlock return the position of the top-left cell of the block. - */ -describe("firstCellOfBlock", () => { - test("firstCellOfBlock", () => { - expect(firstCellOfBlock(0)).toStrictEqual([0, 0]); - expect(firstCellOfBlock(1)).toStrictEqual([0, 3]); - expect(firstCellOfBlock(2)).toStrictEqual([0, 6]); - expect(firstCellOfBlock(4)).toStrictEqual([3, 3]); - expect(firstCellOfBlock(8)).toStrictEqual([6, 6]); - }); -}); - -/** - * This test evaluates that blockIndex return a list containing index of all cells of the same block. - */ -describe("blockIndex", () => { - test("blockIndex", () => { - expect(blockIndex(0)).toStrictEqual([0, 1, 2, 9, 10, 11, 18, 19, 20]); - expect(blockIndex(1)).toStrictEqual([3, 4, 5, 12, 13, 14, 21, 22, 23]); - expect(blockIndex(2)).toStrictEqual([6, 7, 8, 15, 16, 17, 24, 25, 26]); - expect(blockIndex(4)).toStrictEqual([30, 31, 32, 39, 40, 41, 48, 49, 50]); - expect(blockIndex(8)).toStrictEqual([60, 61, 62, 69, 70, 71, 78, 79, 80]); - }); -}); - /** * This test evaluates that getValue return the right initial value. */ -describe("getValue", () => { - test("getValue", () => { - const initVal = [ - "6", - "", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const envs = Array(9); - const newvals = Array(9); - for (let i = 0; i < 9; i++) { - envs[i] = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId" + i) - ); - newvals[i] = new crdtlib.crdt.MVRegister(); - newvals[i].set(String(i + 1), envs[i].tick()); - grid.state.cells[1].merge(newvals[i]); - } - expect(grid.getValue(0)).toBe("6"); - expect(grid.getValue(1)).toBe("1 2 3 4 5 6 7 8 9"); - expect(grid.getValue(2)).toBe(""); - }); -}); - -/** - * This test evaluates that handleChange change the value of the cell. - * Call to getValue should return the new value (2). - */ -describe("handleChange", () => { - test("handleChange", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = mount(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.getValue(4)).toBe("8"); - grid.handleChange(4, "2"); - expect(grid.getValue(4)).toBe("2"); - }); -}); - -/** - * This test evaluates that checkLine return true only if - * the array contains all numbers from 1 to 9 without repeat. - */ -describe("checkLine", () => { - test("checkLine", () => { - const initVal = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "1", - "2", - "3", - "", - "5", - "6", - "7", - "8", - "9", - "1", - "1", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId1") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[45].merge(newval); - - expect(grid.checkLine(0)).toBe(true); - expect(grid.checkLine(1)).toBe(true); - expect(grid.checkLine(2)).toBe(true); - expect(grid.checkLine(3)).toBe(false); - expect(grid.checkLine(4)).toBe(true); - expect(grid.checkLine(5)).toBe(true); - }); -}); - -/** - * This test evaluates that checkColumn return true only if - * the array contains all numbers from 1 to 9 without repeat. - */ -describe("checkColumn", () => { - test("checkColumn", () => { - const initVal = [ - "", - "1", - "1", - "1", - "8", - "7", - "9", - "1", - "4", - "", - "2", - "2", - "1", - "5", - "2", - "3", - "6", - "8", - "", - "3", - "3", - "3", - "9", - "6", - "2", - "5", - "7", - "", - "4", - "", - "4", - "3", - "5", - "4", - "7", - "6", - "", - "5", - "5", - "5", - "7", - "4", - "8", - "3", - "1", - "", - "6", - "6", - "6", - "6", - "1", - "5", - "2", - "9", - "", - "7", - "7", - "7", - "2", - "9", - "", - "", - "3", - "", - "8", - "8", - "8", - "1", - "8", - "7", - "9", - "5", - "", - "9", - "9", - "9", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId1") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[5].merge(newval); - - expect(grid.checkColumn(0)).toBe(true); - expect(grid.checkColumn(1)).toBe(true); - expect(grid.checkColumn(2)).toBe(true); - expect(grid.checkColumn(3)).toBe(false); - expect(grid.checkColumn(4)).toBe(true); - expect(grid.checkColumn(5)).toBe(true); - }); -}); - -/** - * This test evaluates that checkBlock return true only if - * the array contains all numbers from 1 to 9 without repeat. - */ -describe("checkBlock", () => { - test("checkBlock", () => { - const initVal = [ - "", - "", - "", - "1", - "2", - "3", - "1", - "2", - "3", - "", - "", - "", - "4", - "5", - "6", - "4", - "5", - "", - "", - "", - "", - "7", - "8", - "9", - "7", - "8", - "9", - "1", - "1", - "2", - "9", - "3", - "5", - "4", - "7", - "6", - "3", - "4", - "5", - "2", - "7", - "4", - "8", - "3", - "1", - "6", - "7", - "8", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[3].merge(newval); - - expect(grid.checkBlock(0)).toBe(true); - expect(grid.checkBlock(1)).toBe(true); - expect(grid.checkBlock(2)).toBe(true); - expect(grid.checkBlock(3)).toBe(false); - expect(grid.checkBlock(4)).toBe(true); - expect(grid.checkBlock(5)).toBe(true); - }); -}); - -/** - * This test evaluates that validateLine return false if a line contains an error. - */ -describe("validateLine", () => { - /** - * This test evaluates that validateLine return true with a line containing full empty cell. - */ - test("validateLine empty", () => { - const initVal = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateLine()).toBe(true); - }); - - /** - * This test evaluates that validateLine return true with a line containing all numbers - * from 1 to 9 without repeat. - */ - test("validateLine complete", () => { - const initVal = [ - "6", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateLine()).toBe(true); - }); - - /** - * This test evaluates that validateLine return true if a line is missing a value. - */ - test("validateLine with missing values", () => { - const initVal = [ - "6", - "", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateLine()).toBe(true); - }); - - /** - * This test evaluates that validateLine return false when a value is dupplicated. - */ - test("validateLine with wrongs values", () => { - const initVal = [ - "6", - "2", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateLine()).toBe(false); - }); - - /** - * This test evaluates that validateLine return true when a cell has multiple values. - */ - test("validateLine with multiples values", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[1].merge(newval); - - expect(grid.validateLine()).toBe(true); +describe("Cells initialization", () => { + test("Cells initialization", () => { + // prettier-ignore + const initVal = ["6","","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + const wrapper = mount(<Grid cells={initVal.map(val =>({ value: val, modifiable: false, error: false }))} isFinished={false} onChange={() => ({})} />); + + expect(wrapper.find({ id: "0" }).instance().value).toBe("6"); + expect(wrapper.find({ id: "1" }).instance().value).toBe(""); + expect(wrapper.find({ id: "3" }).instance().value).toBe("3"); }); }); -/** - * This test evaluates that validateColumn return false if a column contains an error. - */ -describe("validateColumn", () => { - /** - * This test evaluates that validateColumn return true with a column containing full empty cell. - */ - test("validateColumn empty", () => { - const initVal = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateColumn()).toBe(true); - }); - - /** - * This test evaluates that validateColumn return true with a column containing all numbers - * from 1 to 9 without repeat. - */ - test("validateColumn complete", () => { - const initVal = [ - "6", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateColumn()).toBe(true); - }); - - /** - * This test evaluates that validateColumn return true if a column is missing a value. - */ - test("validateColumn with missing values", () => { - const initVal = [ - "6", - "", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateColumn()).toBe(true); - }); - - /** - * This test evaluates that validateColumn return false when a value is dupplicated. - */ - test("validateColumn with wrongs values", () => { - const initVal = [ - "6", - "2", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateColumn()).toBe(false); - }); - - /** - * This test evaluates that validateColumn return true when a cell has multiple values. - */ - test("validateColumn with multiples values", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[1].merge(newval); - - expect(grid.validateColumn()).toBe(true); - }); -}); - -/** - * This test evaluates that validateBlock return false if a line contains an error. - */ -describe("validateBlock", () => { - /** - * This test evaluates that validateBlock return true with a line containing full empty cell. - */ - test("validateBlock empty", () => { - const initVal = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateBlock()).toBe(true); - }); - - /** - * This test evaluates that validateBlock return true with a line containing all numbers - * from 1 to 9 without repeat. - */ - test("validateBlock complete", () => { - const initVal = [ - "6", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateBlock()).toBe(true); - }); - - /** - * This test evaluates that validateBlock return true if a line is missing a value. - */ - test("validateBlock with missing values", () => { - const initVal = [ - "6", - "", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateBlock()).toBe(true); - }); - - /** - * This test evaluates that validateBlock return false when a value is dupplicated. - */ - test("validateBlock with wrongs values", () => { - const initVal = [ - "6", - "2", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateBlock()).toBe(false); - }); - - /** - * This test evaluates that validateBlock return true when a cell has multiple values. - */ - test("validateBlock with multiples values", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[1].merge(newval); - - expect(grid.validateBlock()).toBe(true); - }); -}); - -/** - * This test evaluates that validateSudoku return false if a line, column or block contains an error. - */ -describe("validateSudoku", () => { - /** - * This test evaluates that validateSudoku return true with a full empty grid. - */ - test("validateSudoku empty", () => { - const initVal = [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateSudoku()).toBe("Continue"); - }); - - /** - * This test evaluates that validateSudoku return true with all lines, columns and blocks - * containing all numbers from 1 to 9 without repeat. - */ - test("validateSudoku complete", () => { - const initVal = [ - "6", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateSudoku()).toBe("Complete"); - }); - - /** - * This test evaluates that validateSudoku return true if the grid is missing a value. - */ - test("validateSudoku with missing values", () => { - const initVal = [ - "6", - "", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateSudoku()).toBe("Continue"); - }); - - /** - * This test evaluates that validateSudoku return false when a value is dupplicated. - */ - test("validateSudoku with wrongs values", () => { - const initVal = [ - "6", - "2", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - expect(grid.validateSudoku()).toBe("Error"); - }); - +describe("Testing cells", () => { /** - * This test evaluates that validateSudoku return true when a cell has multiple values. + * This test evaluates that the grid display the right numbers of Cell. */ - test("validateSudoku with multiples values", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - const wrapper = shallow(<Grid initial={initVal} />); - const grid = wrapper.instance(); - - const env = new crdtlib.utils.SimpleEnvironment( - new crdtlib.utils.ClientUId("myClientId") - ); - const newval = new crdtlib.crdt.MVRegister(); - newval.set("1", env.tick()); - grid.state.cells[1].merge(newval); - - expect(grid.validateSudoku()).toBe("Continue"); + test("Number of cells", () => { + // prettier-ignore + const initVal = ["6","5","","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","","","3", + "2","3","4","6","1","8","7","9","5", + "","6","5","7","4","3","1","8","2"] + + const wrapper = shallow(<Grid cells={initVal.map(val =>({ value: val, modifiable: false, error: false }))} isFinished={false} onChange={() => ({})} />); + expect(wrapper.find("Cell")).toHaveLength(81); }); }); describe("Testing status", () => { - /** - * This test evaluates that the status field display "Continue" when the grid - * is missing a value without wrong entries. - */ - test("UI status continue", () => { - const initVal = [ - "", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = shallow(<Grid initial={initVal} />); - const status = wrapper.find(".status"); - expect(status.text()).toBe("Status : Continue"); - }); - - /** - * This test evaluates that the status field display "Error" when the grid - * contains an error. - */ - test("UI status error", () => { - const initVal = [ - "6", - "2", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = shallow(<Grid initial={initVal} />); - const status = wrapper.find(".status"); - expect(status.text()).toBe("Status : Error"); - }); - /** * This test evaluates that the status field display "Complete" when the grid * is complete without errors. */ - test("UI status complete", () => { - const initVal = [ - "6", - "5", - "2", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "6", - "4", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "9", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = shallow(<Grid initial={initVal} />); - const status = wrapper.find(".status"); - expect(status.text()).toBe("Status : Complete"); + test("Completed grid", () => { + // prettier-ignore + const initVal = ["6","5","2","3","8","7","9","1","4", + "7","1","9","4","5","2","3","6","8", + "3","4","8","1","9","6","2","5","7", + "8","2","1","9","3","5","4","7","6", + "5","9","6","2","7","4","8","3","1", + "4","7","3","8","6","1","5","2","9", + "1","8","7","5","2","9","6","4","3", + "2","3","4","6","1","8","7","9","5", + "9","6","5","7","4","3","1","8","2"] + + const wrapper = mount(<Grid cells={initVal.map(val =>({ value: val, modifiable: false, error: false }))} isFinished={true} onChange={() => ({})} />); + const status = wrapper.find({ id: "status" }); + expect(status.text()).toBe("Sudoku completed"); }); }); -describe("Testing cells", () => { - /** - * This test evaluates that the grid display the right numbers of Cell. - */ - test("Number of cells", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = shallow(<Grid initial={initVal} />); - expect(wrapper.find("Cell")).toHaveLength(81); - }); - - /** - * This test evaluates that the grid display the right initial value. - */ - test("Cell initialization", () => { - const initVal = [ - "6", - "5", - "", - "3", - "8", - "7", - "9", - "1", - "4", - "7", - "1", - "9", - "4", - "5", - "2", - "3", - "6", - "8", - "3", - "4", - "8", - "1", - "9", - "6", - "2", - "5", - "7", - "8", - "2", - "1", - "9", - "3", - "5", - "4", - "7", - "6", - "5", - "9", - "6", - "2", - "7", - "4", - "8", - "3", - "1", - "4", - "7", - "3", - "8", - "6", - "1", - "5", - "2", - "9", - "1", - "8", - "7", - "5", - "2", - "9", - "", - "", - "3", - "2", - "3", - "4", - "6", - "1", - "8", - "7", - "9", - "5", - "", - "6", - "5", - "7", - "4", - "3", - "1", - "8", - "2", - ]; - - const wrapper = mount(<Grid initial={initVal} />); - expect(wrapper.find("Cell").first().text()).toBe("6"); +/** + * This test evaluates that cellsIndexOfBlock return a list containing index of all cells of the same block. + */ + describe("cellsIndexOfBlock", () => { + test("cellsIndexOfBlock", () => { + expect(cellsIndexOfBlock(0)).toStrictEqual([0, 1, 2, 9, 10, 11, 18, 19, 20]); + expect(cellsIndexOfBlock(1)).toStrictEqual([3, 4, 5, 12, 13, 14, 21, 22, 23]); + expect(cellsIndexOfBlock(2)).toStrictEqual([6, 7, 8, 15, 16, 17, 24, 25, 26]); + expect(cellsIndexOfBlock(4)).toStrictEqual([30, 31, 32, 39, 40, 41, 48, 49, 50]); + expect(cellsIndexOfBlock(8)).toStrictEqual([60, 61, 62, 69, 70, 71, 78, 79, 80]); }); }); diff --git a/collaborative/src/components/Grid.tsx b/collaborative/src/components/Grid.tsx index 33b97a48a17155f556281e4eafbaf2b0663a5d88..dade3e25969254f6eb48988dd06b9b9220b7282f 100644 --- a/collaborative/src/components/Grid.tsx +++ b/collaborative/src/components/Grid.tsx @@ -25,213 +25,22 @@ import React from "react"; import assert from "assert"; import Cell from "./Cell"; -import { validInput, GRIDS } from "../constants"; -import { client } from "@concordant/c-client"; -import Submit1Input from "./Submit1Input"; /** * Interface for the properties of the Grid */ interface IGridProps { - session: any; // eslint-disable-line @typescript-eslint/no-explicit-any - collection: any; // eslint-disable-line @typescript-eslint/no-explicit-any -} - -/** - * Interface for the state of the Grid - */ -interface IGridState { - gridNum: string; - mvmap: any; // eslint-disable-line @typescript-eslint/no-explicit-any cells: { value: string; modifiable: boolean; error: boolean }[]; - isConnected: boolean; isFinished: boolean; + onChange: ((index: number, value: string) => void); } /** * This class represent the grid of the Sudoku */ -class Grid extends React.Component<IGridProps, IGridState> { - timerID!: NodeJS.Timeout; - modifiedCells: string[]; - - constructor(props: IGridProps) { - super(props); - const cells = new Array(81) - .fill(null) - .map(() => ({ value: "", modifiable: false, error: false })); - this.modifiedCells = new Array(81).fill(null); - const gridNum = "1"; - const mvmap = this.props.collection.open( - "grid" + gridNum, - "MVMap", - false, - function () { - return; - } - ); - this.state = { - gridNum: gridNum, - mvmap: mvmap, - cells: cells, - isConnected: true, - isFinished: false, - }; - } - - /** - * Called after the component is rendered. - * It set a timer to refresh cells values. - */ - componentDidMount(): void { - this.initFrom(generateStaticGrid(this.state.gridNum)); - this.timerID = setInterval(() => this.updateGrid(), 1000); - } - - /** - * Called when the compenent is about to be removed from the DOM. - * It remove the timer set in componentDidMount(). - */ - componentWillUnmount(): void { - clearInterval(this.timerID); - } - - /** - * Update cells values from C-Client. - */ - updateGrid(): void { - const cells = this.state.cells; - if (!this.state.isConnected) { - console.error("updateGrid() called while not connected."); - return; - } - for (let index = 0; index < 81; index++) { - if (cells[index].modifiable) { - cells[index].value = ""; - } - } - this.props.session.transaction(client.utils.ConsistencyLevel.None, () => { - const itString = this.state.mvmap.iteratorString(); - while (itString.hasNext()) { - const val = itString.next(); - cells[val.first].value = hashSetToString(val.second); - } - }); - this.updateState(cells); - } - - /** - * This function is used to simulate the offline mode. - */ - switchConnection(): void { - if (this.state.isConnected) { - this.modifiedCells = new Array(81).fill(null); - clearInterval(this.timerID); - } else { - for (let index = 0; index < 81; index++) { - if ( - this.state.cells[index].modifiable && - this.modifiedCells[index] !== null - ) { - this.props.session.transaction( - client.utils.ConsistencyLevel.None, - () => { - this.state.mvmap.setString(index, this.modifiedCells[index]); - } - ); - } - } - this.timerID = setInterval(() => this.updateGrid(), 1000); - } - this.setState({ isConnected: !this.state.isConnected }); - } - - /** - * 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 = ""; - if (this.state.isConnected) { - this.props.session.transaction( - client.utils.ConsistencyLevel.None, - () => { - this.state.mvmap.setString(index, cells[index].value); - } - ); - } else { - this.modifiedCells[index] = ""; - } - } - } - 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. - */ +class Grid extends React.Component<IGridProps> { 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); - - if (this.state.isConnected) { - this.props.session.transaction(client.utils.ConsistencyLevel.None, () => { - this.state.mvmap.setString(index, value); - }); - } else { - this.modifiedCells[index] = value; - } - } - - /** - * This handler is called when a new grid number is submit. - * @param gridNum Desired grid number. - */ - handleSubmit(gridNum: string): void { - if ( - Number(gridNum) < 1 || - Number(gridNum) > 100 || - gridNum === this.state.gridNum - ) { - return; - } - const mvmap = this.props.collection.open( - "grid" + gridNum, - "MVMap", - false, - function () { - return; - } - ); - this.setState({ gridNum: gridNum, mvmap: mvmap }); - this.initFrom(generateStaticGrid(gridNum)); + this.props.onChange(index, value); } /** @@ -243,13 +52,13 @@ class Grid extends React.Component<IGridProps, IGridState> { return ( <Cell index={index} - value={this.state.cells[index].value} + value={this.props.cells[index].value} onChange={ - this.state.cells[index].modifiable + this.props.cells[index].modifiable ? (index: number, value: string) => this.handleChange(index, value) : null } - error={this.state.cells[index].error} + error={this.props.cells[index].error} /> ); } @@ -260,7 +69,7 @@ class Grid extends React.Component<IGridProps, IGridState> { */ renderBlock(blockNum: number): JSX.Element { assert.ok(blockNum >= 0 && blockNum < 9); - const index = blockIndex(blockNum); + const index = cellsIndexOfBlock(blockNum); return ( <td> {this.renderCell(index[0])} @@ -283,27 +92,7 @@ class Grid extends React.Component<IGridProps, IGridState> { */ render(): JSX.Element { return ( - <div className="sudoku"> - <div>Current grid : {this.state.gridNum}</div> - <Submit1Input - inputName="Grid" - onSubmit={this.handleSubmit.bind(this)} - /> - <div> - Difficulty levels: easy (1-20), medium (21-40), hard (41-60), - very-hard (61-80), insane (81-100) - </div> - <br /> - <div> - <button onClick={this.reset.bind(this)}>Reset</button> - </div> - <br /> - <div> - <button onClick={() => this.switchConnection()}> - {this.state.isConnected ? "Disconnect" : "Connect"} - </button> - </div> - <br /> + <div> <table className="grid"> <tbody> {[0, 1, 2].map((line) => ( @@ -315,7 +104,7 @@ class Grid extends React.Component<IGridProps, IGridState> { ))} </tbody> </table> - {this.state.isFinished && ( + {this.props.isFinished && ( <h2 className="status" id="status"> Sudoku completed </h2> @@ -323,168 +112,13 @@ class Grid extends React.Component<IGridProps, IGridState> { </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; - } - cpt[Number(val) - 1]++; - } - return cpt.every((c) => c <= 1); - } - - /** - * Check if a column respect Sudoku columns rules. - * @param column The column number to be checked. - */ - checkColumn(column: number): boolean { - assert.ok(column >= 0 && column < 9); - const cpt = Array(9).fill(0); - for (let line = 0; line < 9; line++) { - const index = line * 9 + column; - const val = this.state.cells[index].value; - if (val.length === 0 || val.length > 1) { - continue; - } - cpt[Number(val) - 1]++; - } - return cpt.every((c) => c <= 1); - } - - /** - * Check if a block respect Sudoku blocks rules. - * @param block The block number to be checked. - */ - checkBlock(block: number): boolean { - assert.ok(block >= 0 && block < 9); - const cpt = Array(9).fill(0); - const indexList = blockIndex(block); - for (const index of indexList) { - const val = this.state.cells[index].value; - if (val.length === 0 || val.length > 1) { - continue; - } - cpt[Number(val) - 1]++; - } - return cpt.every((c) => c <= 1); - } - - /** - * This function check if all lines respect Sudoku lines rules. - */ - checkLines(): number[] { - const indexList = []; - for (let line = 0; line < 9; line++) { - if (this.checkLine(line) === false) { - for (let column = 0; column < 9; column++) { - indexList.push(line * 9 + column); - } - } - } - return indexList; - } - - /** - * This function check if all columns respect Sudoku columns rules. - */ - checkColumns(): number[] { - const indexList = []; - for (let column = 0; column < 9; column++) { - if (this.checkColumn(column) === false) { - for (let line = 0; line < 9; line++) { - indexList.push(line * 9 + column); - } - } - } - return indexList; - } - - /** - * This function check if all blocks respect Sudoku blocks rules. - */ - checkBlocks(): number[] { - let indexList: number[] = []; - for (let block = 0; block < 9; block++) { - if (this.checkBlock(block) === false) { - indexList = indexList.concat(blockIndex(block)); - } - } - return indexList; - } - - /** - * This function check if cells contains multiple values. - */ - checkCells(): number[] { - const indexList = []; - for (let cell = 0; cell < 81; cell++) { - const val = this.state.cells[cell].value; - if (val.length > 1) { - indexList.push(cell); - } - } - return indexList; - } - - /** - * Check if all cells respect Sudoku rules and update cells. - * @param cells Current cells values. - */ - updateState( - cells: { value: string; modifiable: boolean; error: boolean }[] - ): void { - let errorIndexList = this.checkLines(); - errorIndexList = errorIndexList.concat(this.checkColumns()); - errorIndexList = errorIndexList.concat(this.checkBlocks()); - errorIndexList = errorIndexList.concat(this.checkCells()); - - const errorIndexSet = new Set(errorIndexList); - - for (let index = 0; index < 81; index++) { - if (errorIndexSet.has(index)) { - cells[index].error = true; - } else { - cells[index].error = false; - } - } - - if (errorIndexSet.size) { - this.setState({ cells: cells, isFinished: false }); - return; - } - - for (let index = 0; index < 81; index++) { - if (!validInput.test(this.state.cells[index].value)) { - this.setState({ cells: cells, isFinished: false }); - return; - } - } - this.setState({ cells: cells, isFinished: true }); - } -} - -/** - * Return a predefined Sudoku grid as a string. - * @param gridNum Desired grid number - */ -function generateStaticGrid(gridNum: string) { - return GRIDS[gridNum]; } /** * Return an array containing all cell index of a block. * @param block The block number of which we want the cells index. */ -function blockIndex(block: number) { +export function cellsIndexOfBlock(block: number): number[] { assert.ok(block >= 0 && block < 9); const line = Math.floor(block / 3) * 3; const column = (block % 3) * 3; @@ -502,21 +136,4 @@ function blockIndex(block: number) { return index; } -/** - * Concatenates all values of a HashSet as a String. - * @param set HashSet to be concatenated. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function hashSetToString(set: any): string { - const res = new Set(); - const it = set.iterator(); - while (it.hasNext()) { - const val = it.next(); - if (val !== "") { - res.add(val); - } - } - return Array.from(res).sort().join(" "); -} - export default Grid;