From 0ef19e1f0b548c3ddff43bd4f8f8a84027cbc5ac Mon Sep 17 00:00:00 2001 From: Maxim Kremnev Date: Sat, 1 Aug 2020 19:19:23 +0300 Subject: [PATCH 1/3] Lesson 4: react component --- src/index.tsx | 2 + src/lesson4/Cell.css | 23 +++++ src/lesson4/Cell.stories.tsx | 17 ++++ src/lesson4/Cell.test.ts | 16 +++ src/lesson4/Cell.tsx | 17 ++++ src/lesson4/Field.css | 10 ++ src/lesson4/Field.stories.tsx | 186 ++++++++++++++++++++++++++++++++++ src/lesson4/Field.test.ts | 25 +++++ src/lesson4/Field.tsx | 21 ++++ src/lesson4/Interfaces.ts | 9 ++ tsconfig.json | 2 +- 11 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 src/lesson4/Cell.css create mode 100644 src/lesson4/Cell.stories.tsx create mode 100644 src/lesson4/Cell.test.ts create mode 100644 src/lesson4/Cell.tsx create mode 100644 src/lesson4/Field.css create mode 100644 src/lesson4/Field.stories.tsx create mode 100644 src/lesson4/Field.test.ts create mode 100644 src/lesson4/Field.tsx create mode 100644 src/lesson4/Interfaces.ts diff --git a/src/index.tsx b/src/index.tsx index e69de29..36580fd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -0,0 +1,2 @@ +import React from 'react'; +import { render } from 'react-dom'; diff --git a/src/lesson4/Cell.css b/src/lesson4/Cell.css new file mode 100644 index 0000000..b239643 --- /dev/null +++ b/src/lesson4/Cell.css @@ -0,0 +1,23 @@ +.cell { + background: #fff; + border: 1px solid #999; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; + display: inline-block; +} + +.cell-empty { + border-color: gray; +} + +.cell-filled { + border-color: #fff; + background: black; +} diff --git a/src/lesson4/Cell.stories.tsx b/src/lesson4/Cell.stories.tsx new file mode 100644 index 0000000..ca223fe --- /dev/null +++ b/src/lesson4/Cell.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Cell from './Cell'; +import { action } from '@storybook/addon-actions'; +import { withKnobs, text, number } from '@storybook/addon-knobs'; + +export default { + title: 'Lesson 4 / Cell', + decorators: [withKnobs], +}; + +export const nonFilled = () => [ + , +]; + +export const Filled = () => [ + , +]; diff --git a/src/lesson4/Cell.test.ts b/src/lesson4/Cell.test.ts new file mode 100644 index 0000000..f9190dc --- /dev/null +++ b/src/lesson4/Cell.test.ts @@ -0,0 +1,16 @@ +import { mount } from 'enzyme'; +import { getCell } from './Cell'; + +test.each` + inputfilled | expected + ${true} | ${'
'} + ${false} | ${'
'} +`('renders cell for empty or filled', ({ inputfilled, expected }) => { + expect( + mount( + getCell({ + filled: inputfilled, + }), + ).html(), + ).toBe(expected); +}); diff --git a/src/lesson4/Cell.tsx b/src/lesson4/Cell.tsx new file mode 100644 index 0000000..a80b7e0 --- /dev/null +++ b/src/lesson4/Cell.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react'; +import './Cell.css'; +import type { CellProps } from './Interfaces'; + +const Cell: FC = ({ filled, x, y }) => { + if (filled) { + return
{filled}
; + } + + return
; +}; + +export default Cell; + +export function getCell(props: CellProps) { + return ; +} diff --git a/src/lesson4/Field.css b/src/lesson4/Field.css new file mode 100644 index 0000000..99705aa --- /dev/null +++ b/src/lesson4/Field.css @@ -0,0 +1,10 @@ +p { + margin: 0; + padding: 0; + line-height: 0; +} +div { + margin: 0; + padding: 0; + line-height: 0; +} diff --git a/src/lesson4/Field.stories.tsx b/src/lesson4/Field.stories.tsx new file mode 100644 index 0000000..a743416 --- /dev/null +++ b/src/lesson4/Field.stories.tsx @@ -0,0 +1,186 @@ +import React from 'react'; +import Field from './Field'; +import { action } from '@storybook/addon-actions'; +import { withKnobs, text, number, object } from '@storybook/addon-knobs'; + +export default { + title: 'Lesson 4 / Field', + decorators: [withKnobs], +}; + +export const emptyCell = () => [ + , +]; + +export const notEmptyCell = () => [ + , +]; diff --git a/src/lesson4/Field.test.ts b/src/lesson4/Field.test.ts new file mode 100644 index 0000000..20d95f0 --- /dev/null +++ b/src/lesson4/Field.test.ts @@ -0,0 +1,25 @@ +import { mount } from 'enzyme'; +import { getField } from './Field'; + +test.each` + inputfilled | expected_br | expected_div | expected_cell_empty | expected_cell_filled + ${[ + [true, false, true, true, false, true, true, true, false, true], + [true, true, true, true, false, false, false, true, false, true], +]} | ${2} | ${20} | ${7} | ${13} +`( + 'testing rendering field', + ({ + inputfilled, + expected_br, + expected_div, + expected_cell_empty, + expected_cell_filled, + }) => { + const field = mount(getField({ field: inputfilled })); + expect(field.find('p').length).toBe(expected_br); + expect(field.find('.cell').length).toBe(expected_div); + expect(field.find('.cell-empty').length).toBe(expected_cell_empty); + expect(field.find('.cell-filled').length).toBe(expected_cell_filled); + }, +); diff --git a/src/lesson4/Field.tsx b/src/lesson4/Field.tsx new file mode 100644 index 0000000..d30f12c --- /dev/null +++ b/src/lesson4/Field.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import Cell from './Cell'; +import './Field.css'; +import type { FieldProps } from './Interfaces'; + +const Field: FC = ({ field }) => ( +
+ {field.map((row, y) => [ + ...row.map((filled: boolean, x) => ( + + )), + y !== row.length - 1 ?

: null, + ])} +

+); + +export default Field; + +export function getField(props: FieldProps) { + return ; +} diff --git a/src/lesson4/Interfaces.ts b/src/lesson4/Interfaces.ts new file mode 100644 index 0000000..6ecc75c --- /dev/null +++ b/src/lesson4/Interfaces.ts @@ -0,0 +1,9 @@ +export interface CellProps { + filled?: boolean; + x?: number; + y?: number; +} + +export interface FieldProps { + field: boolean[][]; +} diff --git a/tsconfig.json b/tsconfig.json index 4ef2a6d..40d51d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": true /* Allow javascript files to be compiled. */, From 2ce6ab8c843df703eacb7c618ce8a42b88092fcc Mon Sep 17 00:00:00 2001 From: Maxim Kremnev Date: Tue, 4 Aug 2020 18:43:10 +0300 Subject: [PATCH 2/3] Added component gameoflifeproto --- src/lesson4/Cell.stories.tsx | 4 +- src/lesson4/{Cell.test.ts => Cell.test.tsx} | 15 +- src/lesson4/Cell.tsx | 4 - src/lesson4/Field.stories.tsx | 198 +++--------------- src/lesson4/{Field.test.ts => Field.test.tsx} | 5 +- src/lesson4/Field.tsx | 4 - src/lesson4/GameOfLifeProto.css | 3 + src/lesson4/GameOfLifeProto.stories.tsx | 17 ++ src/lesson4/GameOfLifeProto.test.tsx | 5 + src/lesson4/GameOfLifeProto.tsx | 75 +++++++ 10 files changed, 137 insertions(+), 193 deletions(-) rename src/lesson4/{Cell.test.ts => Cell.test.tsx} (52%) rename src/lesson4/{Field.test.ts => Field.test.tsx} (86%) create mode 100644 src/lesson4/GameOfLifeProto.css create mode 100644 src/lesson4/GameOfLifeProto.stories.tsx create mode 100644 src/lesson4/GameOfLifeProto.test.tsx create mode 100644 src/lesson4/GameOfLifeProto.tsx diff --git a/src/lesson4/Cell.stories.tsx b/src/lesson4/Cell.stories.tsx index ca223fe..0cdcc99 100644 --- a/src/lesson4/Cell.stories.tsx +++ b/src/lesson4/Cell.stories.tsx @@ -9,9 +9,9 @@ export default { }; export const nonFilled = () => [ - , + , ]; export const Filled = () => [ - , + , ]; diff --git a/src/lesson4/Cell.test.ts b/src/lesson4/Cell.test.tsx similarity index 52% rename from src/lesson4/Cell.test.ts rename to src/lesson4/Cell.test.tsx index f9190dc..9b984c8 100644 --- a/src/lesson4/Cell.test.ts +++ b/src/lesson4/Cell.test.tsx @@ -1,16 +1,13 @@ -import { mount } from 'enzyme'; -import { getCell } from './Cell'; +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import Cell from './Cell'; test.each` inputfilled | expected ${true} | ${'
'} ${false} | ${'
'} `('renders cell for empty or filled', ({ inputfilled, expected }) => { - expect( - mount( - getCell({ - filled: inputfilled, - }), - ).html(), - ).toBe(expected); + const cellItem = mount(); + + expect(cellItem.html()).toBe(expected); }); diff --git a/src/lesson4/Cell.tsx b/src/lesson4/Cell.tsx index a80b7e0..713f335 100644 --- a/src/lesson4/Cell.tsx +++ b/src/lesson4/Cell.tsx @@ -11,7 +11,3 @@ const Cell: FC = ({ filled, x, y }) => { }; export default Cell; - -export function getCell(props: CellProps) { - return ; -} diff --git a/src/lesson4/Field.stories.tsx b/src/lesson4/Field.stories.tsx index a743416..551724f 100644 --- a/src/lesson4/Field.stories.tsx +++ b/src/lesson4/Field.stories.tsx @@ -1,186 +1,40 @@ import React from 'react'; import Field from './Field'; import { action } from '@storybook/addon-actions'; -import { withKnobs, text, number, object } from '@storybook/addon-knobs'; +import { + withKnobs, + text, + number, + object, + boolean, +} from '@storybook/addon-knobs'; +import type { FieldProps } from './Interfaces'; export default { title: 'Lesson 4 / Field', decorators: [withKnobs], }; +const cellGridFillRandom = (cellStatus = () => Math.random() < 0.3) => { + const grid: boolean[][] = []; + for (let y = 0; y < 10; y++) { + grid[y] = []; + for (let x = 0; x < 10; x++) { + grid[y][x] = cellStatus(); + } + } + return grid; +}; + +const cellGridNonFill = () => + Array.from({ length: 10 }).fill( + Array.from({ length: 10 }).fill(false), + ); + export const emptyCell = () => [ - , + , ]; export const notEmptyCell = () => [ - , + , ]; diff --git a/src/lesson4/Field.test.ts b/src/lesson4/Field.test.tsx similarity index 86% rename from src/lesson4/Field.test.ts rename to src/lesson4/Field.test.tsx index 20d95f0..ddf360f 100644 --- a/src/lesson4/Field.test.ts +++ b/src/lesson4/Field.test.tsx @@ -1,5 +1,6 @@ +import React from 'react'; import { mount } from 'enzyme'; -import { getField } from './Field'; +import Field from './Field'; test.each` inputfilled | expected_br | expected_div | expected_cell_empty | expected_cell_filled @@ -16,7 +17,7 @@ test.each` expected_cell_empty, expected_cell_filled, }) => { - const field = mount(getField({ field: inputfilled })); + const field = mount(); expect(field.find('p').length).toBe(expected_br); expect(field.find('.cell').length).toBe(expected_div); expect(field.find('.cell-empty').length).toBe(expected_cell_empty); diff --git a/src/lesson4/Field.tsx b/src/lesson4/Field.tsx index d30f12c..56d9089 100644 --- a/src/lesson4/Field.tsx +++ b/src/lesson4/Field.tsx @@ -15,7 +15,3 @@ const Field: FC = ({ field }) => ( ); export default Field; - -export function getField(props: FieldProps) { - return ; -} diff --git a/src/lesson4/GameOfLifeProto.css b/src/lesson4/GameOfLifeProto.css new file mode 100644 index 0000000..9a2646e --- /dev/null +++ b/src/lesson4/GameOfLifeProto.css @@ -0,0 +1,3 @@ +.btn { + margin: 20px 0; +} diff --git a/src/lesson4/GameOfLifeProto.stories.tsx b/src/lesson4/GameOfLifeProto.stories.tsx new file mode 100644 index 0000000..6961d50 --- /dev/null +++ b/src/lesson4/GameOfLifeProto.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { withKnobs, number, array } from '@storybook/addon-knobs'; +import Field from './Field'; +import GameOfLifeProto from './GameOfLifeProto'; +export default { + title: 'Lesson 4 / GameOfLifeProto', + decorators: [withKnobs], +}; + +const players = ['x', 'y']; +export const GameOfLifePrototip = () => ( + +); diff --git a/src/lesson4/GameOfLifeProto.test.tsx b/src/lesson4/GameOfLifeProto.test.tsx new file mode 100644 index 0000000..46bbd1b --- /dev/null +++ b/src/lesson4/GameOfLifeProto.test.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import GameOfLifeProto from './GameOfLifeProto'; + +describe('GameOfLifeComponent testing', () => {}); diff --git a/src/lesson4/GameOfLifeProto.tsx b/src/lesson4/GameOfLifeProto.tsx new file mode 100644 index 0000000..41ef891 --- /dev/null +++ b/src/lesson4/GameOfLifeProto.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import './GameOfLifeProto.css'; + +type FieldPropsComponent = React.FC<{ + field: boolean[][]; + onClick: () => void; +}>; + +interface FieldPropsInterface { + rows: number; + columns: number; + fieldComponent: FieldPropsComponent; +} + +interface FieldState { + fieldState: boolean[][]; +} + +const cellGridFillRandom = ( + rows: number, + columns: number, + cellStatus = () => Math.random() < 0.3, +) => { + const grid: boolean[][] = []; + for (let y = 0; y < rows; y++) { + grid[y] = []; + for (let x = 0; x < columns; x++) { + grid[y][x] = cellStatus(); + } + } + return grid; +}; + +class GameOfLifeProto extends React.Component { + private fieldComponent: FieldPropsComponent; + + constructor(props: FieldPropsInterface) { + super(props); + this.fieldComponent = props.fieldComponent; + this.state = { + fieldState: cellGridFillRandom(this.props.rows, this.props.columns), + }; + this.onClick = this.onClick.bind(this); + } + + public onClick() { + this.setState((state) => { + const cloneFieldState = cellGridFillRandom( + this.props.rows, + this.props.columns, + ); + + return { + fieldState: cloneFieldState, + }; + }); + } + + render() { + const FieldComponent = this.fieldComponent; + return ( + <> + + + + ); + } +} + +export default GameOfLifeProto; From 34f1b741870c109d4da4ddceaf0f9e91fc402f7c Mon Sep 17 00:00:00 2001 From: Maxim Kremnev Date: Sun, 9 Aug 2020 22:55:52 +0300 Subject: [PATCH 3/3] Remove context for a methods --- src/lesson4/Cell.stories.tsx | 10 +++++----- src/lesson4/Cell.test.tsx | 10 +++++----- src/lesson4/Cell.tsx | 2 +- src/lesson4/Field.stories.tsx | 17 +++++------------ src/lesson4/Field.test.tsx | 24 ++++++++++++------------ src/lesson4/Field.tsx | 4 ++-- src/lesson4/GameOfLifeProto.stories.tsx | 9 +++++---- src/lesson4/GameOfLifeProto.tsx | 11 +++-------- 8 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/lesson4/Cell.stories.tsx b/src/lesson4/Cell.stories.tsx index 0cdcc99..35a4394 100644 --- a/src/lesson4/Cell.stories.tsx +++ b/src/lesson4/Cell.stories.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import Cell from './Cell'; -import { action } from '@storybook/addon-actions'; -import { withKnobs, text, number } from '@storybook/addon-knobs'; +import { Cell } from './Cell'; +import { withKnobs, number } from '@storybook/addon-knobs'; +// eslint-disable-next-line no-restricted-syntax export default { title: 'Lesson 4 / Cell', decorators: [withKnobs], }; export const nonFilled = () => [ - , + , ]; export const Filled = () => [ - , + , ]; diff --git a/src/lesson4/Cell.test.tsx b/src/lesson4/Cell.test.tsx index 9b984c8..4ced06b 100644 --- a/src/lesson4/Cell.test.tsx +++ b/src/lesson4/Cell.test.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import { mount, shallow } from 'enzyme'; -import Cell from './Cell'; +import { mount } from 'enzyme'; +import { Cell } from './Cell'; test.each` - inputfilled | expected + inputFilled | expected ${true} | ${'
'} ${false} | ${'
'} -`('renders cell for empty or filled', ({ inputfilled, expected }) => { - const cellItem = mount(); +`('renders cell for empty or filled', ({ inputFilled, expected }) => { + const cellItem = mount(); expect(cellItem.html()).toBe(expected); }); diff --git a/src/lesson4/Cell.tsx b/src/lesson4/Cell.tsx index 713f335..6c3a4c3 100644 --- a/src/lesson4/Cell.tsx +++ b/src/lesson4/Cell.tsx @@ -10,4 +10,4 @@ const Cell: FC = ({ filled, x, y }) => { return
; }; -export default Cell; +export { Cell }; diff --git a/src/lesson4/Field.stories.tsx b/src/lesson4/Field.stories.tsx index 551724f..c437d62 100644 --- a/src/lesson4/Field.stories.tsx +++ b/src/lesson4/Field.stories.tsx @@ -1,15 +1,8 @@ import React from 'react'; -import Field from './Field'; -import { action } from '@storybook/addon-actions'; -import { - withKnobs, - text, - number, - object, - boolean, -} from '@storybook/addon-knobs'; -import type { FieldProps } from './Interfaces'; +import { Field } from './Field'; +import { withKnobs, object } from '@storybook/addon-knobs'; +// eslint-disable-next-line no-restricted-syntax export default { title: 'Lesson 4 / Field', decorators: [withKnobs], @@ -32,9 +25,9 @@ const cellGridNonFill = () => ); export const emptyCell = () => [ - , + , ]; export const notEmptyCell = () => [ - , + , ]; diff --git a/src/lesson4/Field.test.tsx b/src/lesson4/Field.test.tsx index ddf360f..22845e8 100644 --- a/src/lesson4/Field.test.tsx +++ b/src/lesson4/Field.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { mount } from 'enzyme'; -import Field from './Field'; +import { Field } from './Field'; test.each` - inputfilled | expected_br | expected_div | expected_cell_empty | expected_cell_filled + inputFilled | expectedBr | expectedDiv | expectedCellEmpty | expectedCellFilled ${[ [true, false, true, true, false, true, true, true, false, true], [true, true, true, true, false, false, false, true, false, true], @@ -11,16 +11,16 @@ test.each` `( 'testing rendering field', ({ - inputfilled, - expected_br, - expected_div, - expected_cell_empty, - expected_cell_filled, + inputFilled, + expectedBr, + expectedDiv, + expectedCellEmpty, + expectedCellFilled, }) => { - const field = mount(); - expect(field.find('p').length).toBe(expected_br); - expect(field.find('.cell').length).toBe(expected_div); - expect(field.find('.cell-empty').length).toBe(expected_cell_empty); - expect(field.find('.cell-filled').length).toBe(expected_cell_filled); + const field = mount(); + expect(field.find('p').length).toBe(expectedBr); + expect(field.find('.cell').length).toBe(expectedDiv); + expect(field.find('.cell-empty').length).toBe(expectedCellEmpty); + expect(field.find('.cell-filled').length).toBe(expectedCellFilled); }, ); diff --git a/src/lesson4/Field.tsx b/src/lesson4/Field.tsx index 56d9089..8d35853 100644 --- a/src/lesson4/Field.tsx +++ b/src/lesson4/Field.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import Cell from './Cell'; +import { Cell } from './Cell'; import './Field.css'; import type { FieldProps } from './Interfaces'; @@ -14,4 +14,4 @@ const Field: FC = ({ field }) => ( ); -export default Field; +export { Field }; diff --git a/src/lesson4/GameOfLifeProto.stories.tsx b/src/lesson4/GameOfLifeProto.stories.tsx index 6961d50..f9e8f1b 100644 --- a/src/lesson4/GameOfLifeProto.stories.tsx +++ b/src/lesson4/GameOfLifeProto.stories.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import { withKnobs, number, array } from '@storybook/addon-knobs'; -import Field from './Field'; -import GameOfLifeProto from './GameOfLifeProto'; +import { withKnobs, number } from '@storybook/addon-knobs'; +import { Field } from './Field'; +import { GameOfLifeProto } from './GameOfLifeProto'; + +// eslint-disable-next-line no-restricted-syntax export default { title: 'Lesson 4 / GameOfLifeProto', decorators: [withKnobs], }; -const players = ['x', 'y']; export const GameOfLifePrototip = () => ( void; }>; interface FieldPropsInterface { @@ -40,11 +39,10 @@ class GameOfLifeProto extends React.Component { this.state = { fieldState: cellGridFillRandom(this.props.rows, this.props.columns), }; - this.onClick = this.onClick.bind(this); } public onClick() { - this.setState((state) => { + this.setState(() => { const cloneFieldState = cellGridFillRandom( this.props.rows, this.props.columns, @@ -60,10 +58,7 @@ class GameOfLifeProto extends React.Component { const FieldComponent = this.fieldComponent; return ( <> - + @@ -72,4 +67,4 @@ class GameOfLifeProto extends React.Component { } } -export default GameOfLifeProto; +export { GameOfLifeProto };