Attention une mise à jour du service Gitlab va être effectuée le mardi 30 novembre entre 17h30 et 18h00. Cette mise à jour va générer une interruption du service dont nous ne maîtrisons pas complètement la durée mais qui ne devrait pas excéder quelques minutes. Cette mise à jour intermédiaire en version 14.0.12 nous permettra de rapidement pouvoir mettre à votre disposition une version plus récente.

Commit 1ad842c7 authored by Ludovic Le Frioux's avatar Ludovic Le Frioux
Browse files

Merge branch '15-setup-linter' into 'dev'

Resolve "Setup linter"

See merge request !19
parents 109600bc d7a0b7d8
build
\ No newline at end of file
module.exports = {
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,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {},
};
......@@ -21,3 +21,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd collaborative
npm run pre-commit
# dependencies
/node_modules
package-lock.json
# testing
/coverage
# production
/build
......@@ -13,7 +13,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/);
- The project uses the CRDT library from Concordant private registry: [see Usage section](https://gitlab.inria.fr/concordant/software/c-crdtlib/-/blob/master/README.md).
1.**Install Project dependencies**
1.**Install Project dependencies**
Go to project root directory and:
......
This diff is collapsed.
{
"name": "c-sudoku",
"description": "Sudoku Demo with CRDT",
"version": "1.0.0",
"author": "Yannick Li <yannick.li@concordant.io>",
"license": "MIT",
"homepage": "./",
"dependencies": {
"@concordant/c-client": "^1.0.0",
"@concordant/c-crdtlib": "^1.0.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.1.0",
"assert": "^2.0.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-helmet": "^6.1.0",
"react-scripts": "^4.0.3",
"typescript": "^4.0.3",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5"
}
"name": "c-sudoku",
"description": "Sudoku Demo with CRDT",
"version": "1.0.0",
"author": "Yannick Li <yannick.li@concordant.io>",
"license": "MIT",
"homepage": "./",
"dependencies": {
"@concordant/c-client": "^1.0.0",
"@concordant/c-crdtlib": "^1.0.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.1.0",
"assert": "^2.0.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-helmet": "^6.1.0",
"react-scripts": "^4.0.3",
"typescript": "^4.0.3",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"prepare": "cd .. && husky install collaborative/.husky",
"test": "react-scripts test",
"lint": "eslint .",
"pre-commit": "lint-staged --verbose",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint": "^7.24.0",
"eslint-plugin-react": "^7.23.2",
"prettier": "2.2.1",
"husky": "^6.0.0",
"lint-staged": "^10.5.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json,yml}": "prettier --write"
}
}
......@@ -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={(index:number, value:string) => {}}
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={(index:number, value:string) => {}}
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={(index:number, value:string) => {}}
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={(index:number, value:string) => {}}
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,50 +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: any,
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: any) {
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)
}
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
);
}
}
render() {
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={this.props.onChange ? (event) => this.onChange(event) : function() { }}
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,39 +22,44 @@
* SOFTWARE.
*/
import React from 'react';
import Grid from './Grid';
import { client } from '@concordant/c-client';
import React from "react";
import Grid from "./Grid";
import { client } from "@concordant/c-client";
import CONFIG from "../config.json";
/**
* Interface for the state of a Game.
* Keep a reference to the opened session and opened MVMap.
*/
interface IGameState {
session: any,
collection: any
session: client.Session;
collection: client.Collection;
}
/**
* This class represent the Game that glues all components together.
*/
class Game extends React.Component<{}, IGameState> {
constructor(props: any) {
super(props);
let CONFIG = require('../config.json');
let session = client.Session.Companion.connect(CONFIG.dbName, CONFIG.serviceUrl, CONFIG.credentials);
let collection = session.openCollection("sudoku", false);
this.state = {
session: session,
collection: collection
}
}
class Game extends React.Component<Record<string, unknown>, IGameState> {
constructor(props: Record<string, unknown>) {
super(props);
const session = client.Session.Companion.connect(
CONFIG.dbName,
CONFIG.serviceUrl,
CONFIG.credentials
);
const collection = session.openCollection("sudoku", false);
this.state = {
session: session,
collection: collection,
};
}
render() {
return (
<Grid session={this.state.session} collection={this.state.collection} />
);
}
render(): JSX.Element {
return (
<Grid session={this.state.session} collection={this.state.collection} />
);
}
}
export default Game
export default Game;
This diff is collapsed.
This diff is collapsed.
......@@ -22,20 +22,20 @@
* SOFTWARE.
*/
import React from 'react';
import React from "react";
/**
* This class represent the header of the site.
*/
class Header extends React.Component {
render() {
return (
<div className="title background-turquoise padding">
<h1>C - Sudoku</h1>
<h2>Sudoku using Concordant CRDTs</h2>
</div>
)
}
render(): JSX.Element {
return (
<div className="title background-turquoise padding">
<h1>C - Sudoku</h1>
<h2>Sudoku using Concordant CRDTs</h2>
</div>
);
}
}
export default Header
export default Header;
{
"dbName": "concordant",
"serviceUrl": "http://localhost:4000",
"credentials": "credentials"
}
\ No newline at end of file
"dbName": "concordant",
"serviceUrl": "https://demo.concordant.io/c-service",
"credentials": "credentials"
}
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment