Commit aed0ca16 authored by Ludovic Le Frioux's avatar Ludovic Le Frioux
Browse files

Merge branch 'dev' into 'master'

Resolve "Release v1.0.1"

Closes #25

See merge request !24
parents 56d70c4b e28875f7
Pipeline #272888 passed with stages
in 0 seconds
stages:
- single-player-pipeline
- collaborative-pipeline
single-player:
stage: single-player-pipeline
trigger:
include: single-player/.gitlab-ci.yml
strategy: depend
only:
changes:
- single-player/**/*
collaborative:
stage: collaborative-pipeline
trigger:
include: collaborative/.gitlab-ci.yml
strategy: depend
only:
changes:
- collaborative/**/*
......@@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Unreleased]
- Added
- Add README file in the top-level repository
- Changed
- Update specific README
- Deprecated
- Removed
- Fixed
- Security
- [1.0.1] - 2021-05-17
- Changed
- Update documentation
- Fixed
- Fix concurrency between edits and remote updates
......@@ -8,3 +8,5 @@ This repository contains two versions of Sudoku :
- A collaborative version, using the Concordant platform API.
This is the outcome of the tutorial and can be tried on our
[sudoku demo page](https://demo.concordant.io/c-sudoku/index.html).
The main repository is in [the Inria gitlab](https://gitlab.inria.fr/concordant/software/c-sudoku/) (this is where you can post tickets). There is an identical clone in [GitHub](https://github.com/concordant/software/c-sudoku/) (tickets here might be ignored).
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
image: node:14.16.0
stages:
- build
- test
- predeploy
- deploy
cache: &global_cache # per-branch cache
key: ${CI_COMMIT_REF_SLUG}
paths:
- collaborative/.npm/
# make push explicit
policy: pull
build:
stage: build
before_script:
- cd collaborative
script:
- npm ci --cache .npm --prefer-offline
# prepare script is skipped by install/ci when running NPM 6 as root.
# This line can be safely removed with NPM 7+ (see NPM Changelog)
- npm run prepare
# Build demo application
# Still some ESlint warnings to be fixed: disable ESlint at this stage
- DISABLE_ESLINT_PLUGIN=true npm run build
artifacts:
paths:
- collaborative/node_modules/
- collaborative/build/
expire_in: 3 days
cache:
<<: *global_cache
policy: pull-push
test:
stage: test
# node 15+ has bug #2143 : https://github.com/npm/cli/issues/2143
image: node:14.16.0
variables:
COUCHDB_HOST: couchdb
COUCHDB_PORT: 5984
COUCHDB_URL: http://$COUCHDB_HOST:$COUCHDB_PORT/
COUCHDB_USER: admin
COUCHDB_PASSWORD: "admin"
services:
- couchdb:latest
before_script:
- cd collaborative
# setup couchdb
- curl -su "$COUCHDB_USER:$COUCHDB_PASSWORD"
-X PUT $COUCHDB_URL/_users
# run c-service in background
- npx @concordant/c-service &
script:
- npm test
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
artifacts:
paths:
- collaborative/coverage/
# Filter out node_modules from build artifacts to speed up deploy.
# Gitlab 13.10 will allow to use multiple cache instead.
pre-deploy:
stage: predeploy
rules:
- if: "$CI_COMMIT_BRANCH"
# use only artifacts from build job
dependencies:
- build
# cache is not needed: don't download
cache: {}
# don't download repository content
variables:
GIT_STRATEGY: none
artifacts:
paths:
- collaborative/build/
# script is required
script:
- echo Exporting collaborative/build/…
deploy_review:
stage: deploy
rules:
- if: "$CI_COMMIT_BRANCH"
tags:
- webserver
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://demo.concordant.io/c-sudoku_${CI_ENVIRONMENT_SLUG}/
on_stop: stop_review
# use only artifacts from pre-deploy job
dependencies:
- pre-deploy
# cache and repository art not needed: don't download
cache: {}
variables:
GIT_STRATEGY: none
script:
- rm -rf /var/www/demo.concordant.io/html/c-sudoku_${CI_ENVIRONMENT_SLUG}
- mv collaborative/build /var/www/demo.concordant.io/html/c-sudoku_${CI_ENVIRONMENT_SLUG}
stop_review:
stage: deploy
rules:
- if: "$CI_COMMIT_BRANCH"
when: manual
allow_failure: true
tags:
- webserver
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
# artifacts, cache and repository are not needed: don't download
dependencies: []
cache: {}
variables:
GIT_STRATEGY: none
script:
- rm -rf /var/www/demo.concordant.io/html/c-sudoku_${CI_ENVIRONMENT_SLUG}
deploy_live:
stage: deploy
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
when: manual
tags:
- webserver
environment:
name: live
url: https://demo.concordant.io/c-sudoku/
# use only artifacts from pre-deploy job
dependencies:
- pre-deploy
# cache and repository art not needed: don't download
cache: {}
variables:
GIT_STRATEGY: none
script:
- rm -rf /var/www/demo.concordant.io/html/c-sudoku
- mv collaborative/build /var/www/demo.concordant.io/html/c-sudoku
#!/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.1",
"author": "Yannick Li <yannick.li@concordant.io>",
"license": "MIT",
"homepage": "./",
"dependencies": {
"@concordant/c-client": "~1.2.0",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^13.1.9",
"@types/jest": "^26.0.23",
"@types/node": "^14.14.45",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.1.1",
"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.2.4",
"web-vitals": "^1.1.2"
},
"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.23.0",
"@typescript-eslint/parser": "^4.23.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint": "^7.26.0",
"eslint-plugin-react": "^7.23.2",
"husky": "^6.0.0",
"lint-staged": "^10.5.4",
"prettier": "2.2.1"
},
"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,83 +23,27 @@
*/
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", () => {
/**
* 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", () => {
const wrapper = mount(<Cell
index={3}
value={"6"}
onChange={(index:number, value:string) => {}}
lock={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={(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")
});
/**
* 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={(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")
});
/**
* 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")
test("Cell value", () => {
const wrapper = mount(
<Cell index={3} value={"6"} onChange={() => ({})} error={false} />
);
const input = wrapper.find("input");
expect(input.instance().value).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,51 @@ 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);
}
}
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}