Skip to content

Commit

Permalink
Merge pull request #5 from 21GramConsulting/bug/3
Browse files Browse the repository at this point in the history
Bug/3
  • Loading branch information
rlegmann committed Apr 14, 2023
2 parents 27516fd + c7b671d commit c8af21a
Show file tree
Hide file tree
Showing 18 changed files with 1,434 additions and 23 deletions.
1,039 changes: 1,024 additions & 15 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
},
"license": "MIT",
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.0",
"@types/react": "17.0.0",
"@types/react": "^17.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"eslint": "^8.37.0",
Expand All @@ -47,6 +50,7 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"react": "17.0.2",
"react-test-renderer": "^17.0.2",
"ts-jest": "^29.0.5",
Expand Down Expand Up @@ -81,7 +85,8 @@
],
"moduleNameMapper": {
"^#(.*)$": "<rootDir>/useTaskQueue/$1",
"^!(.*)$": "<rootDir>/test/$1"
"^!(.*)$": "<rootDir>/test/$1",
"^@21gram-consulting/use-task-queue": "<rootDir>/useTaskQueue/index.ts"
},
"coverageThreshold": {
"global": {
Expand All @@ -99,4 +104,4 @@
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @jest-environment jsdom
*/
import {descriptors} from '#consistencyGuard';
import {render, screen} from '@testing-library/react';
import '@testing-library/jest-dom';
import ReproApp from './ReproApp';
import userEvent from '@testing-library/user-event';

beforeEach(() => {
descriptors.clear();
});
afterEach(() => {
jest.clearAllMocks();
});

describe('Hook Reevaluation due to task recreation:', () => {
test("shouldn't end up in an infinite loop when a failure occurs.", async () => {
render(<ReproApp />);
let repetition = 50;
while (repetition--) {
await userEvent.click(screen.getByText('Reproduce Bug'));
}
await screen.findByTestId('output');

expect(screen.getByTestId('output')).toHaveTextContent('For an input');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {useReproContext} from './ReproContext';
import {FunctionComponent} from 'react';
import {TaskQueueHook} from '@21gram-consulting/use-task-queue';
import Task1 from './Task1';
import Task2 from './Task2';
import Task3 from './Task3';

const OngoingTasks: FunctionComponent = function OngoingTasks() {
const context = useReproContext();
return (
<div>
<h1>Ongoing Tasks</h1>
<div>
<h2>{subTitle(context.task1)}</h2>
<Task1 />
</div>
<div>
<h2>{subTitle(context.task2)}</h2>
<Task2 />
</div>
<div>
<h2>{subTitle(context.task3)}</h2>
<Task3 />
</div>
</div>
);
};

const subTitle = <A, B>({
input,
process,
error,
output,
}: TaskQueueHook<A, B>): string =>
[
`${input.length} waiting to be picked up`,
`${process.length} is processed.`,
`${error.length} resolved as error`,
`${output.length} result ready for pickup`,
]
.join(', ')
.concat('.');

export default OngoingTasks;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {FunctionComponent} from 'react';
import {ReproProvider} from './ReproContext';
import OngoingTasks from './OngoingTasks';
import Reproduce from './Reproduce';

const ReproApp: FunctionComponent = function ReproApp() {
return (
<ReproProvider>
<OngoingTasks />
<Reproduce />
</ReproProvider>
);
};

export default ReproApp;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
FunctionComponent,
PropsWithChildren,
createContext,
useContext,
} from 'react';
import {nullTaskQueue, TaskQueueHook} from '@21gram-consulting/use-task-queue';
import useTask1 from './useTask1';
import useTask2 from './useTask2';
import useTask3 from './useTask3';

type Props = PropsWithChildren<unknown>;
export type ContextValue = {
task1: TaskQueueHook<number, number>;
task2: TaskQueueHook<number, string>;
task3: TaskQueueHook<string, string[]>;
};

const ReproContext = createContext<ContextValue>({
task1: nullTaskQueue(),
task2: nullTaskQueue(),
task3: nullTaskQueue(),
});

export function useReproContext(): ContextValue {
return useContext(ReproContext);
}

export const ReproProvider: FunctionComponent<Props> = props => {
const task1 = useTask1();
const task2 = useTask2(task1);
const task3 = useTask3(task2);
return (
<ReproContext.Provider value={{task1, task2, task3}}>
{props.children}
</ReproContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {FunctionComponent, useState} from 'react';
import {useReproContext} from './ReproContext';

const Reproduce: FunctionComponent = () => {
const context = useReproContext();
const [input, setInput] = useState(10);
const reproduce = () => {
context.task1.push(input);
setInput(v => v + 1);
};
return (
<>
<button onClick={reproduce}>Reproduce Bug</button>
<output data-testid="output">
<ul>
{context.task3.output.map((output, index) => (
<li key={index}>
For an input of {output.input}, the outputs are:
<ul>
{output.output.map((output, index) => (
<li key={index}>{output}</li>
))}
</ul>
</li>
))}
</ul>
</output>
</>
);
};

export default Reproduce;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {FunctionComponent} from 'react';
import {TaskProcess} from '@21gram-consulting/use-task-queue';
import {useReproContext} from './ReproContext';

const Task1: FunctionComponent = function Task1() {
const context = useReproContext();
return (
<div>
<h3>Task 1</h3>
<div>
{context.task1.process.map((process, index) => (
<TaskItem
{...process}
key={index}
cancel={() => context.task1.kill(process)}
/>
))}
</div>
</div>
);
};

type TaskItemProps = TaskProcess<number, number> & {
cancel: () => void;
};
const TaskItem: FunctionComponent<TaskItemProps> = props => {
return (
<div>
<h4>Task Item</h4>
<p>Processing input: {props.input}</p>
<button onClick={props.cancel}>Cancel</button>
</div>
);
};

export default Task1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {FunctionComponent} from 'react';
import {TaskProcess} from '@21gram-consulting/use-task-queue';
import {useReproContext} from './ReproContext';

const Task2: FunctionComponent = function Task2() {
const context = useReproContext();
return (
<div>
<h3>Task 1</h3>
<div>
{context.task2.process.map((process, index) => (
<TaskItem
{...process}
key={index}
cancel={() => context.task2.kill(process)}
/>
))}
</div>
</div>
);
};

type TaskItemProps = TaskProcess<number, string> & {
cancel: () => void;
};
const TaskItem: FunctionComponent<TaskItemProps> = props => {
return (
<div>
<h4>Task Item</h4>
<p>Processing input: {props.input}</p>
<button onClick={props.cancel}>Cancel</button>
</div>
);
};

export default Task2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {FunctionComponent} from 'react';
import {TaskProcess} from '@21gram-consulting/use-task-queue';
import {useReproContext} from './ReproContext';

const Task3: FunctionComponent = function Task3() {
const context = useReproContext();
return (
<div>
<h3>Task 1</h3>
<div>
{context.task3.process.map((process, index) => (
<TaskItem
{...process}
key={index}
cancel={() => context.task3.kill(process)}
/>
))}
</div>
</div>
);
};

type TaskItemProps = TaskProcess<string, string[]> & {
cancel: () => void;
};
const TaskItem: FunctionComponent<TaskItemProps> = props => {
return (
<div>
<h4>Task Item</h4>
<p>Processing input: {props.input}</p>
<button onClick={props.cancel}>Cancel</button>
</div>
);
};

export default Task3;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {TaskQueueHook, useTaskQueue} from '@21gram-consulting/use-task-queue';
import {json} from '@21gram-consulting/ts-codec';

export default function useTask1(): TaskQueueHook<number, number> {
return useTaskQueue({
name: 'task1',
codec: json.number,
task: v => {
if (v % 3 === 0) throw new Error('Stub task execution error.');
return [v, v * 2, v * 3];
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {TaskQueueHook, useTaskQueue} from '@21gram-consulting/use-task-queue';
import {json} from '@21gram-consulting/ts-codec';

export default function useTask2(
input: TaskQueueHook<any, number>
): TaskQueueHook<number, string> {
return useTaskQueue({
name: 'task2',
codec: json.number,
input: input,
task: v => [v.toString()],
precondition: v => v % 2 === 0,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {TaskQueueHook, useTaskQueue} from '@21gram-consulting/use-task-queue';
import {json} from '@21gram-consulting/ts-codec';

export default function useTask3(
input: TaskQueueHook<any, string>
): TaskQueueHook<string, string[]> {
return useTaskQueue({
name: 'task3',
codec: json.string,
input: input,
task: v => [v.split('')],
postcondition: v => v.length === 2,
});
}
Loading

0 comments on commit c8af21a

Please sign in to comment.