elements, each of which acts as a container for a single book's data. The total number of .book elements that will be rendered should be equal to the number of search results. For each book that is displayed containing the class .book, all fields available in the props.books object (author, title, pages, etc) should be displayed.
42 | A series of
elements. These elements are used to filter the search results and present them in the same order as props.books. The inputs must preserve the following class names:
43 |
44 |
45 |
46 |
47 |
48 | Expected behavior
49 | This demo illustrates the expected behavior of the component.
50 |
51 | The props the same as the example props which the Web Preview is populated with by default. You can view and change these in your workspace in ./src/App.jsx.
52 |
53 | bs.gif
54 |
55 | Current behavior
56 | Currently, changing values in the input boxes doesn't filter books as expected. Your task is to fix the code to correctly filter books and make the test suite pass.
57 |
58 | Rubric
59 | You'll be evaluated on how efficiently you're able to debug the code and pass the provided tests. All relevant tests have been provided, so you needn't worry about hidden edge cases.
60 |
61 | Resources
62 | Feel free to consult documentation or external internet resources for JS and React.
--------------------------------------------------------------------------------
/1/src/Calculator.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class Calculator extends React.Component {
4 | constructor() {
5 | super();
6 | this.state = {
7 | result: "",
8 | };
9 | }
10 |
11 | onClick = (button) => {
12 | if (button === "=") {
13 | this.calculate();
14 | } else if (button === "C") {
15 | this.reset();
16 | } else if(button === "+" || button === "-" || button === "*" || button === "/") {
17 | if(this.state.result === ""){
18 | this.setState({
19 | result: "",
20 | });
21 | }
22 | var length = this.state.result.length;
23 | var temp = this.state.result;
24 | if(this.state.result.charAt(length-1)!=="+" || this.state.result.charAt(length-1)!=="/" || this.state.result.charAt(length-1)!=="*" || this.state.result.charAt(length-1)!=="-"){
25 | this.setState({
26 | result: this.state.result + button,
27 | });
28 | }
29 | if(this.state.result.charAt(length-1)==="+" || this.state.result.charAt(length-1)==="/" || this.state.result.charAt(length-1)==="*" || this.state.result.charAt(length-1)==="-"){
30 | temp = temp.slice(0,-1);
31 | temp+=button;
32 | this.setState({
33 | result:temp,
34 | })
35 | }
36 | } else {
37 | this.setState({
38 | result: this.state.result + button,
39 | });
40 | }
41 | };
42 |
43 | calculate = () => {
44 | try {
45 | const newResult = eval(this.state.result);
46 | this.setState({result: newResult});
47 | } catch (e) {
48 | this.setState({data: 'error'})
49 | }
50 | }
51 |
52 | reset = () => {
53 | this.setState({
54 | result: "",
55 | });
56 | };
57 |
58 | backspace = () => {
59 | this.setState({
60 | result: this.state.result.slice(0, -1),
61 | });
62 | };
63 |
64 | render() {
65 | return (
66 |
67 |
68 |
69 | {this.state.result}
70 |
71 |
72 |
this.onClick(e.target.name)} class="digit-1">
73 | 1
74 |
75 |
this.onClick(e.target.name)} class="digit-2">
76 | 2
77 |
78 |
this.onClick(e.target.name)} class="digit-3">
79 | 3
80 |
81 |
this.onClick(e.target.name)} class="op-add">
82 | +
83 |
84 |
85 |
86 |
this.onClick(e.target.name)} class="digit-4">
87 | 4
88 |
89 |
this.onClick(e.target.name)} class="digit-5">
90 | 5
91 |
92 |
this.onClick(e.target.name)} class="digit-6">
93 | 6
94 |
95 |
this.onClick(e.target.name)} class="op-sub">
96 | -
97 |
98 |
99 |
100 |
this.onClick(e.target.name)} class="digit-7">
101 | 7
102 |
103 |
this.onClick(e.target.name)} class="digit-8">
104 | 8
105 |
106 |
this.onClick(e.target.name)} class="digit-9">
107 | 9
108 |
109 |
this.onClick(e.target.name)} class="op-mul">
110 | x
111 |
112 |
113 |
114 |
this.onClick(e.target.name)} class="clear">
115 | C
116 |
117 |
this.onClick(e.target.name)} class="digit-0">
118 | 0
119 |
120 |
this.onClick(e.target.name)} class="eq">
121 | =
122 |
123 |
this.onClick(e.target.name)} class="op-div">
124 | ÷
125 |
126 |
127 |
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/1/Task.txt:
--------------------------------------------------------------------------------
1 | React: Calculator
2 | Calculators are always handy to have in the browser! In this challenge, you'll make a calculator application with basic functionality. The capabilities will be limited in two important ways:
3 |
4 | All division operations will be floored, eliminating decimals and floats.
5 | The calculator will respond to button presses to constrain the input to remain valid, as described below.
6 | Buttons on the calculator
7 | The calculator will feature a traditional, button-based interface and with no bells or whistles (no parenthesis, decimals, or special functions). There will be 16 buttons on the calculator:
8 |
9 | A set of numerical buttons permitting entry of digits 0-9.
10 | A set of four operation buttons corresponding to addition +, subtraction -, multiplication * and floor integer division /.
11 | An equals = button, which evaluates the expression shown on the display.
12 | A clear c button, which resets the calculator to a blank state.
13 | Rendering your calculator
14 | Layout, CSS and use of child components is up to you. The deliverables which the testing suite requires are specific classes which should be attached to buttons and the output element. Here is the comprehensive list of these elements and classes which the Calculator component must render for the testing suite to function:
15 |
16 |
contains a depiction of the current state of the calculator. It can only render characters corresponding those available on the below buttons.
17 | , ... (one for each digit from 0-9) enable the user to enter numbers.
18 | , , and enable the user to enter a mathematical operator (addition, subtraction, multiplication and division, respectively).
19 | enables the user to compute the result of an expression.
20 | enables the user to reset the state of the calculator.
21 | Permitted operations
22 | The operation of the calculator must adhere to a set of specifications described in this section.
23 |
24 | The display portion of the calculator should respond to button presses according to the following rules:
25 |
26 | The display starts out showing the empty string. This is the state which the calculator returns to upon the user clicking c (clear) or a computation attempting division by zero.
27 | When the display is empty, the only buttons that will work are the digit buttons and the - subtraction button (which acts as a negative sign in this context). Any other button will have no effect.
28 | When the last entered data is a digit, any button will work.
29 | If a digit is the last data entered and another digit is pressed, the new digit will be appended to the display.
30 | If a digit is the last data entered and an operator button is pressed, the operator will be appended to the display.
31 | Pressing = should only compute the result of the expression if the last entered character is a digit.
32 | When the last entered character is an operator, it's permissible to follow it with any button except =. New digits should be appended and new operators will be handled as follows:
33 | If the trailing operator is + or -, all operator presses will replace the old trailing operator with the new operator. For example, if the display shows 5- and the / button was pressed, the display should show 5/.
34 | If the trailing operator is * or /, all operator presses will replace the old trailing operator with the new operator with the exception of -, which will be appended to the display, making it possible to create legal display states such as 6/-.
35 | Once the display enters a state with two consecutive operators such as 6/-, any further operator presses can be ignored entirely until a digit is pressed.
36 | No leading zeroes are allowed in any circumstance (the single digit 0 is not considered to have leading zeroes, but 00 or 08 are illegal numbers). When a button is pushed that would create a leading zero, replace the zero with the new number. For example, if the display shows 5+0 and 8 is pressed, the display should show 5+8.
37 | If any number is divided by zero, the calculator should reset to the blank origin state as if c had been pressed.
38 | If a computation has been performed (i.e. the = button has just been pushed and the display contains a single number), clicking an operator button will extend the result as the first operand of a new expression, but pressing a digit will begin a new expression as if the display state had been clear.
39 | Illegal display states include:
40 |
41 | * or +67+8 (leading operator other than -)
42 | --3, 5+-3, 5-+3, 5/+6 or 67*/8 (two consecutive operators that don't include either a * or / followed by -).
43 | 7*06 or 06*7 (leading zero)
44 | Legal display states include (with results after pressing =):
45 |
46 | -8-9 => -17
47 | -8-9*-6 => 46 (order of operations is respected)
48 | -0*-0 => 0
49 | 0*-0 => 0
50 | 6/0 => (reset to empty string origin state due to division by zero)
51 | 6/-0 => (reset to empty string origin state due to division by zero)
52 | Here are examples of legal states, but are incomplete expressions, so pressing = has no effect:
53 |
54 | -
55 | -5
56 | 5*
57 | 5-
58 | Additional notes
59 | Since the calculator uses floor division for all operations (including intermediate operations), results will differ from division on a normal calculator or as produced by eval. You must implement your own miniature expression parser according to the specification for full credit in this challenge.
60 |
61 | You may assume only small numbers are input; there's no need to worry about integer over- or under-flow in input, output or at any stage of the computation.
62 |
63 | Comprehensive tests will be provided to you. Feel free to make changes to the test suite as you see fit--these changes will be ignored for the final submission.
64 |
65 | CSS and UX are important, but secondary to logical functionality. Although there is no predetermined single solution or set of requirements for style, please take some time to present an interface that shows a fundamental grasp of the domain.
66 |
67 | Demo
68 | This demo exercises the application requirements described above.
69 |
70 | calc.gif
71 |
72 | Remember, style is up to you--there's no need to copy the design shown here.
73 |
74 | Rubric
75 | You'll be evaluated primarily on passing test cases and secondarily on code cleanliness and maintainability.
76 |
77 | Resources
78 | Feel free to consult documentation as needed. Here are a few suggestions to start with:
79 |
80 | Wolfram is useful for determining how floor division should be evaluted. For example, one test case is -12/5=3 which you can try here on Wolfram.
81 | React docs
82 | MDN
--------------------------------------------------------------------------------
/3/BookSearch.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Enzyme, { mount, shallow } from "enzyme";
3 | import Adapter from "enzyme-adapter-react-16";
4 | Enzyme.configure({ adapter: new Adapter() });
5 | import sinon from "sinon";
6 | import { readFileSync } from "fs";
7 | import BookSearch from "../src/BookSearch";
8 |
9 | describe("BookSearch", () => {
10 | const books = JSON.parse(readFileSync("./sample_data/books.json"));
11 | let wrapper;
12 |
13 | beforeEach(() => {
14 | wrapper = mount( );
17 | });
18 | afterEach(() => wrapper.unmount());
19 |
20 | describe("the correct elements were rendered", () => {
21 | it("should have 5 input elements for required search fields", () =>
22 | [
23 | ".author",
24 | ".title",
25 | ".language",
26 | ".country",
27 | ".year",
28 | ].forEach(e => expect(wrapper.exists(e)).toBe(true))
29 | );
30 |
31 | it("should initially show all books", () => {
32 | expect(wrapper.exists(".book")).toBe(true);
33 | expect(wrapper.find(".book")).toHaveLength(books.length);
34 | });
35 |
36 | it("should render data for each field in a book", () => {
37 | const html = wrapper.find(".book").at(0).html();
38 | [
39 | "Chinua Achebe",
40 | "Nigeria",
41 | "English",
42 | "209",
43 | "Things Fall Apart",
44 | "1958"
45 | ].forEach(e => expect(html.includes(e)).toBe(true));
46 | });
47 | });
48 |
49 | describe("searching by each field alone", () => {
50 | describe("modifying the author field", () => {
51 | let input;
52 |
53 | beforeEach(() => {
54 | input = wrapper.find(".author");
55 | input.simulate("change", { target: { value: "woolf" } });
56 | });
57 |
58 | it("should respond to change events", () => {
59 | expect(input.instance().value).toEqual("woolf");
60 | });
61 |
62 | it("should filter books on the author query", () => {
63 | expect(wrapper.find(".book")).toHaveLength(2);
64 | expect(wrapper.find(".book")
65 | .at(0).html().includes("Dalloway")).toBe(true);
66 | expect(wrapper.find(".book")
67 | .at(1).html().includes("Lighthouse")).toBe(true);
68 | });
69 | });
70 |
71 | describe("modifying the title field", () => {
72 | let input;
73 |
74 | beforeEach(() => {
75 | input = wrapper.find(".title");
76 | input.simulate("change", { target: { value: "dea" } });
77 | });
78 |
79 | it("should respond to change events", () => {
80 | expect(input.instance().value).toEqual("dea");
81 | });
82 |
83 | it("should filter books on the author query", () => {
84 | expect(wrapper.find(".book")).toHaveLength(3);
85 | expect(wrapper.find(".book")
86 | .at(0).html().includes("Euripides")).toBe(true);
87 | expect(wrapper.find(".book")
88 | .at(1).html().includes("Nikolai Gogol")).toBe(true);
89 | expect(wrapper.find(".book")
90 | .at(2).html().includes("The Death of Ivan Ilyich")).toBe(true);
91 | });
92 | });
93 |
94 | describe("modifying the country field", () => {
95 | let input;
96 |
97 | beforeEach(() => {
98 | input = wrapper.find(".country");
99 | input.simulate("change", { target: { value: "ITALY" } });
100 | });
101 |
102 | it("should respond to change events", () => {
103 | expect(input.instance().value).toEqual("ITALY");
104 | });
105 |
106 | it("should filter books on the country query", () => {
107 | expect(wrapper.find(".book")).toHaveLength(5);
108 | [
109 | "Dante Alighieri",
110 | "Giovanni Boccaccio",
111 | "Giacomo Leopardi",
112 | "Elsa Morante",
113 | "Italo Svevo"
114 | ].forEach((e, i) =>
115 | expect(wrapper.find(".book")
116 | .at(i).html().includes(e)).toBe(true)
117 | );
118 | });
119 | });
120 |
121 | describe("modifying the language field", () => {
122 | let input;
123 |
124 | beforeEach(() => {
125 | input = wrapper.find(".language");
126 | input.simulate("change", { target: { value: "SPanish" } });
127 | });
128 |
129 | it("should respond to change events", () => {
130 | expect(input.instance().value).toEqual("SPanish");
131 | });
132 |
133 | it("should filter books on the language query", () => {
134 | expect(wrapper.find(".book")).toHaveLength(6);
135 | [
136 | "Jorge Luis Borges",
137 | "Miguel de Cervantes",
138 | "Gypsy Ballads",
139 | "One Hundred Years of Solitude",
140 | "Love in the Time of Cholera",
141 | "Juan Rulfo"
142 | ].forEach((e, i) =>
143 | expect(wrapper.find(".book")
144 | .at(i).html().includes(e)).toBe(true)
145 | );
146 | });
147 | });
148 |
149 | describe("modifying the year field", () => {
150 | let input;
151 |
152 | beforeEach(() => {
153 | input = wrapper.find(".year");
154 | input.simulate("change", { target: { value: "196" } });
155 | });
156 |
157 | it("should respond to change events", () => {
158 | expect(input.instance().value).toEqual("196");
159 | });
160 |
161 | it("should filter books on the language query", () => {
162 | expect(wrapper.find(".book")).toHaveLength(4);
163 | [
164 | "Ficciones",
165 | "One Hundred Years of Solitude",
166 | "The Golden Notebook",
167 | "Season of Migration to the North",
168 | ].forEach((e, i) =>
169 | expect(wrapper.find(".book")
170 | .at(i).html().includes(e)).toBe(true)
171 | );
172 | });
173 | });
174 | });
175 |
176 | describe("filtering on multiple terms", () => {
177 | describe("filtering on 192_ and english", () => {
178 | beforeEach(() => {
179 | wrapper.find(".year").simulate(
180 | "change", { target: { value: "192" } }
181 | );
182 | wrapper.find(".language").simulate(
183 | "change", { target: { value: "english" } }
184 | );
185 | });
186 |
187 | it("should filter books on the language and year query", () => {
188 | expect(wrapper.find(".book")).toHaveLength(4);
189 | [
190 | "Sound and the Fury",
191 | "Ulysses",
192 | "Mrs Dalloway",
193 | "To the Lighthouse",
194 | ].forEach((e, i) =>
195 | expect(wrapper.find(".book")
196 | .at(i).html().includes(e)).toBe(true)
197 | );
198 | });
199 | });
200 |
201 | describe("filtering on all fields with trim", () => {
202 | beforeEach(() => {
203 | wrapper.find(".author").simulate(
204 | "change", { target: { value: " all " } }
205 | );
206 | wrapper.find(".title").simulate(
207 | "change", { target: { value: " t " } }
208 | );
209 | wrapper.find(".country").simulate(
210 | "change", { target: { value: "ni" } }
211 | );
212 | wrapper.find(".language").simulate(
213 | "change", { target: { value: "english" } }
214 | );
215 | wrapper.find(".year").simulate(
216 | "change", { target: { value: "195" } }
217 | );
218 | });
219 |
220 | it("should filter books on all fields", () => {
221 | expect(wrapper.find(".book")).toHaveLength(1);
222 | [
223 | "Edgar Allan Poe",
224 | "United States",
225 | "English",
226 | "842",
227 | "Tales",
228 | "1950"
229 | ].forEach(e =>
230 | expect(wrapper.find(".book")
231 | .at(0).html().includes("")).toBe(true)
232 | );
233 | });
234 | });
235 |
236 | describe("filtering on terms that have no results", () => {
237 | beforeEach(() => {
238 | wrapper.find(".year").simulate(
239 | "change", { target: { value: "190" } }
240 | );
241 | wrapper.find(".language").simulate(
242 | "change", { target: { value: "spanish" } }
243 | );
244 | });
245 |
246 | it("should filter books on the language and year query", () => {
247 | expect(wrapper.find(".book").exists()).toBe(false);
248 | expect(wrapper.find(".book")).toHaveLength(0);
249 | });
250 | });
251 | });
252 | });
--------------------------------------------------------------------------------
/2/DynamicInput.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Enzyme, { mount, shallow } from "enzyme";
3 | import Adapter from "enzyme-adapter-react-16";
4 | Enzyme.configure({ adapter: new Adapter() });
5 | import sinon from "sinon";
6 | import DynamicInput from "../src/DynamicInput";
7 | import { getFocusedElemInputVal } from "../utilities/test-utilities";
8 |
9 | let di;
10 | let addRowBtn;
11 |
12 | const makeTestTable = items => {
13 | for (let i = 0; i < items.length; i++) {
14 | addRowBtn.simulate("click");
15 | }
16 |
17 | items.forEach((e, i) => {
18 | const input = di.find(".row-input").at(i);
19 | input.simulate("change", { target: { value: e } });
20 | });
21 | };
22 |
23 | describe("DynamicInput", () => {
24 | beforeEach(() => {
25 | di = mount( );
26 | addRowBtn = di.find(".add-row");
27 | });
28 | afterEach(() => {
29 | di.unmount();
30 | di = null;
31 | addRowBtn = null;
32 | });
33 |
34 | test('should have an "add-row" button', () => {
35 | expect(addRowBtn.exists()).toBe(true);
36 | expect(addRowBtn).toHaveLength(1);
37 | });
38 |
39 | test('should have no rows before "add-row" is clicked', () => {
40 | [
41 | ".row-input",
42 | ".row-delete",
43 | ".row-up",
44 | ".row-down"
45 | ].forEach(e => expect(di.find(e).exists()).toBe(false));
46 | });
47 |
48 | test('should add a row on pushing the "add-row" button', () => {
49 | addRowBtn.simulate("click");
50 | [
51 | ".row-input",
52 | ".row-delete",
53 | ".row-up",
54 | ".row-down"
55 | ].forEach(e => expect(di.find(e)).toHaveLength(1));
56 | });
57 |
58 | test('should change focus to the new row on clicking the "add-row" button', () => {
59 | addRowBtn.simulate("click");
60 | di.setProps(); // forces a re-render
61 | expect(getFocusedElemInputVal()).toEqual("");
62 | });
63 |
64 | test('should be able to add multiple rows', () => {
65 | for (let i = 1; i < 5; i++) {
66 | addRowBtn.simulate("click");
67 | [
68 | ".row-input",
69 | ".row-delete",
70 | ".row-up",
71 | ".row-down"
72 | ].forEach(e => expect(di.find(e)).toHaveLength(i));
73 | }
74 | });
75 |
76 | test('should be able to add text to the input fields', () => {
77 | const items = [
78 | "apples",
79 | "pears",
80 | "watermelon",
81 | "cantaloupe"
82 | ];
83 | makeTestTable(items);
84 |
85 | items.forEach((e, i) => {
86 | const input = di.find(".row-input").at(i);
87 | expect(input.instance().value).toEqual(e);
88 | });
89 | });
90 |
91 | test('should be able to change the text of an input field', () => {
92 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
93 | const input = di.find(".row-input").at(3);
94 | input.simulate("change", { target: { value: "bananas" } });
95 | expect(input.instance().value).toEqual("bananas");
96 | });
97 |
98 | test('should be able to move a row up', () => {
99 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
100 | di.find(".row-up").at(3).simulate("click");
101 |
102 | [
103 | "apples",
104 | "pears",
105 | "cantaloupe",
106 | "watermelon",
107 | ].forEach((e, i) => {
108 | expect(di.find(".row-input").at(i).instance().value).toEqual(e);
109 | });
110 | });
111 |
112 | test('should focus the correct input element after moving a row up', () => {
113 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
114 | di.find(".row-up").at(3).simulate("click");
115 | di.setProps();
116 | expect(getFocusedElemInputVal()).toEqual("cantaloupe");
117 | });
118 |
119 | test('should be able to move a row down', () => {
120 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
121 | di.find(".row-down").at(0).simulate("click");
122 | [
123 | "pears",
124 | "apples",
125 | "watermelon",
126 | "cantaloupe",
127 | ].forEach((e, i) => {
128 | expect(di.find(".row-input").at(i).instance().value).toEqual(e);
129 | });
130 | });
131 |
132 | test('should focus the correct input element after moving a row down', () => {
133 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
134 | di.find(".row-down").at(0).simulate("click");
135 | di.setProps();
136 | expect(getFocusedElemInputVal()).toEqual("apples");
137 | });
138 |
139 | test('should work when an input field is moved down but is already at the bottom', () => {
140 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
141 | di.find(".row-down").at(3).simulate("click");
142 | [
143 | "apples",
144 | "pears",
145 | "watermelon",
146 | "cantaloupe",
147 | ].forEach((e, i) => {
148 | expect(di.find(".row-input").at(i).instance().value).toEqual(e);
149 | });
150 | });
151 |
152 | test('should focus the last input element after moving a row down that is already at the bottom', () => {
153 | makeTestTable(["apples", "pears", "watermelon", "cantaloupe"]);
154 | di.find(".row-down").at(3).simulate("click");
155 | di.setProps();
156 | expect(getFocusedElemInputVal()).toEqual("cantaloupe");
157 | });
158 |
159 | test('should work when an input field is moved up but is already at the top', () => {
160 | makeTestTable(["apples", "pears"]);
161 | di.find(".row-up").at(0).simulate("click");
162 | [
163 | "apples",
164 | "pears",
165 | ].forEach((e, i) => {
166 | expect(di.find(".row-input").at(i).instance().value).toEqual(e);
167 | });
168 | });
169 |
170 | test('should focus the 0-th input element after moving a row up which is already at the top', () => {
171 | makeTestTable(["apples", "pears"]);
172 | di.find(".row-up").at(0).simulate("click");
173 | di.setProps();
174 | expect(getFocusedElemInputVal()).toEqual("apples");
175 | });
176 |
177 | test('should work when the bottom row is deleted', () => {
178 | makeTestTable(["apples", "pears"]);
179 | di.find(".row-delete").at(1).simulate("click");
180 | expect(di.find(".row-input")).toHaveLength(1);
181 | expect(di.find(".row-input").at(0).instance().value).toEqual("apples");
182 | });
183 |
184 | test('should focus the last input element after deleting the bottom row', () => {
185 | makeTestTable(["apples", "pears", "bananas", "grapefruit"]);
186 | di.find(".row-delete").at(3).simulate("click");
187 | di.setProps();
188 | expect(getFocusedElemInputVal()).toEqual("bananas");
189 | });
190 |
191 | test('should work when a middle row is deleted', () => {
192 | makeTestTable(["pears", "apples", "bananas"]);
193 | di.find(".row-delete").at(1).simulate("click");
194 | expect(di.find(".row-input").at(0).instance().value).toEqual("pears");
195 | expect(di.find(".row-input").at(1).instance().value).toEqual("bananas");
196 | });
197 |
198 | test('should focus the correct input element after deleting a middle row', () => {
199 | makeTestTable(["pears", "apples", "bananas"]);
200 | di.find(".row-delete").at(1).simulate("click");
201 | di.setProps();
202 | expect(getFocusedElemInputVal()).toEqual("bananas");
203 | });
204 |
205 | test('should work when the remaining rows are deleted', () => {
206 | makeTestTable(["pears", "apples"]);
207 | di.find(".row-delete").at(0).simulate("click");
208 | expect(di.find(".row-input").at(0).instance().value).toEqual("apples");
209 | di.find(".row-delete").at(0).simulate("click");
210 | expect(di.find(".row-input").length).toBe(0);
211 | expect(di.find(".row-delete").length).toBe(0);
212 | expect(di.find(".row-up").length).toBe(0);
213 | expect(di.find(".row-down").length).toBe(0);
214 | });
215 |
216 | test('should not focus anything after all rows are deleted', () => {
217 | makeTestTable(["pears", "apples"]);
218 | di.find(".row-delete").at(0).simulate("click");
219 | expect(di.find(".row-input").at(0).instance().value).toEqual("apples");
220 | di.find(".row-delete").at(0).simulate("click");
221 | expect(di.find(".row-input").length).toBe(0);
222 | expect(di.find(".row-delete").length).toBe(0);
223 | expect(di.find(".row-up").length).toBe(0);
224 | expect(di.find(".row-down").length).toBe(0);
225 | di.setProps();
226 | expect(getFocusedElemInputVal()).toBe(undefined);
227 | });
228 |
229 | test('should work when a row is re-added, modified and deleted after clearing the list', () => {
230 | expect(di.find(".row-input").length).toBe(0);
231 | addRowBtn.simulate("click");
232 | expect(di.find(".row-input").length).toBe(1);
233 | expect(di.find(".row-delete").length).toBe(1);
234 | expect(di.find(".row-up").length).toBe(1);
235 | expect(di.find(".row-down").length).toBe(1);
236 | const input = di.find(".row-input").at(0);
237 | input.simulate("change", { target: { value: "cucumber" } });
238 | expect(input.instance().value).toEqual("cucumber");
239 | di.find(".row-up").at(0).simulate("click");
240 | di.find(".row-down").at(0).simulate("click");
241 | expect(input.instance().value).toEqual("cucumber");
242 | di.setProps();
243 | expect(getFocusedElemInputVal()).toEqual("cucumber");
244 | di.find(".row-delete").at(0).simulate("click");
245 | expect(di.find(".row-input").length).toBe(0);
246 | });
247 | });
248 |
--------------------------------------------------------------------------------
/1/Calculator.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Enzyme, { mount, shallow } from "enzyme";
3 | import Adapter from "enzyme-adapter-react-16";
4 | Enzyme.configure({ adapter: new Adapter() });
5 | import Calculator from "../src/Calculator";
6 |
7 | const buttonNames = [
8 | "op-add", "op-mul", "op-div", "op-sub", "clear", "eq",
9 | ...Array(10).fill().map((e, i) => `digit-${i}`),
10 | ];
11 | const getButtons = calc =>
12 | buttonNames.reduce((accumulator, btn) => {
13 | accumulator[btn] = calc.find("." + btn);
14 | return accumulator;
15 | }, {})
16 | ;
17 | let calc;
18 | let buttons;
19 | let output;
20 |
21 | describe('Calculator', () => {
22 | beforeEach(() => {
23 | calc = mount( );
24 | buttons = getButtons(calc);
25 | output = calc.find(".output");
26 | });
27 | afterEach(() => {
28 | calc.unmount();
29 | });
30 |
31 | describe('Output element', () => {
32 | it('should render an element with class name "output"', () => {
33 | expect(calc.find('.output').exists()).toBe(true);
34 | });
35 |
36 | it('should have an empty output display initially', () => {
37 | expect(calc.find(".output").text()).toBe("");
38 | });
39 | });
40 |
41 | describe('Buttons should be rendered and be clickable', () => {
42 | describe('Operator buttons', () => {
43 | for (const op of ["add", "sub", "mul", "div"]) {
44 | test(`should render operator button "op-${op}"`, () => {
45 | expect(calc.find(`.op-${op}`).exists()).toBe(true);
46 | });
47 | }
48 |
49 | it('should render equals button with class "eq"', () => {
50 | expect(calc.find(".eq").exists()).toBe(true);
51 | });
52 |
53 | it('should render clear button with class "clear"', () => {
54 | expect(calc.find(".clear").exists()).toBe(true);
55 | });
56 | });
57 |
58 | describe('Digit buttons', () => {
59 | for (let i = 0; i < 10; i++) {
60 | test(`button for digit ${i} should exist with class "digit-${i}"`, () => {
61 | expect(calc.find(`.digit-${i}`).exists()).toBe(true);
62 | });
63 |
64 | test(`should update output when "digit-${i}" button is clicked`, () => {
65 | calc.find(`.digit-${i}`).simulate("click");
66 | expect(calc.find(".output").text()).toBe("" + i);
67 | });
68 | }
69 | });
70 | });
71 |
72 | describe('Clearing the display', () => {
73 | it('should clear the display when the "clear" button is clicked', () => {
74 | const actions = [
75 | ["digit-9", "9"],
76 | ["clear", ""],
77 | ];
78 |
79 | for (const [op, expected] of actions) {
80 | buttons[op].simulate("click");
81 | expect(output.text()).toBe(expected);
82 | }
83 | });
84 | });
85 |
86 | describe('Simple operations', () => {
87 | it("should work on addition", () => {
88 | const actions = [
89 | ["digit-3", "3"],
90 | ["op-add", "3+"],
91 | ["digit-5", "3+5"],
92 | ["eq", "8"],
93 | ];
94 |
95 | for (const [op, expected] of actions) {
96 | buttons[op].simulate("click");
97 | expect(output.text()).toBe(expected);
98 | }
99 | });
100 |
101 | it("should work on subtraction", () => {
102 | const actions = [
103 | ["digit-3", "3"],
104 | ["op-sub", "3-"],
105 | ["digit-5", "3-5"],
106 | ["eq", "-2"],
107 | ];
108 |
109 | for (const [op, expected] of actions) {
110 | buttons[op].simulate("click");
111 | expect(output.text()).toBe(expected);
112 | }
113 | });
114 |
115 | it("should work on multiplication", () => {
116 | const actions = [
117 | ["digit-3", "3"],
118 | ["op-mul", "3*"],
119 | ["digit-5", "3*5"],
120 | ["eq", "15"],
121 | ];
122 |
123 | for (const [op, expected] of actions) {
124 | buttons[op].simulate("click");
125 | expect(output.text()).toBe(expected);
126 | }
127 | });
128 |
129 | it("should work on integer division", () => {
130 | const actions = [
131 | ["digit-3", "3"],
132 | ["op-div", "3/"],
133 | ["digit-5", "3/5"],
134 | ["eq", "0"],
135 | ];
136 |
137 | for (const [op, expected] of actions) {
138 | buttons[op].simulate("click");
139 | expect(output.text()).toBe(expected);
140 | }
141 | });
142 |
143 | it("should work on an expression containing multi-digit numbers", () => {
144 | const actions = [
145 | ["digit-6", "6"],
146 | ["digit-3", "63"],
147 | ["op-div", "63/"],
148 | ["digit-1", "63/1"],
149 | ["digit-5", "63/15"],
150 | ["eq", "4"],
151 | ];
152 |
153 | for (const [op, expected] of actions) {
154 | buttons[op].simulate("click");
155 | expect(output.text()).toBe(expected);
156 | }
157 | });
158 |
159 | it("should work on an expression with multiple operators", () => {
160 | const actions = [
161 | ["digit-2", "2"],
162 | ["digit-1", "21"],
163 | ["digit-3", "213"],
164 | ["op-sub", "213-"],
165 | ["digit-2", "213-2"],
166 | ["digit-0", "213-20"],
167 | ["digit-0", "213-200"],
168 | ["op-add", "213-200+"],
169 | ["digit-1", "213-200+1"],
170 | ["digit-0", "213-200+10"],
171 | ["eq", "23"],
172 | ["clear", ""],
173 | ];
174 |
175 | for (const [op, expected] of actions) {
176 | buttons[op].simulate("click");
177 | expect(output.text()).toBe(expected);
178 | }
179 | });
180 | });
181 |
182 | describe('Negative numbers', () => {
183 | it("should permit a leading negative sign", () => {
184 | const actions = [
185 | ["op-sub", "-"],
186 | ["digit-1", "-1"],
187 | ["digit-2", "-12"],
188 | ["op-div", "-12/"],
189 | ["digit-5", "-12/5"],
190 | ["eq", "-3"],
191 | ["clear", ""],
192 | ];
193 |
194 | for (const [op, expected] of actions) {
195 | buttons[op].simulate("click");
196 | expect(output.text()).toBe(expected);
197 | }
198 | });
199 |
200 | it("should permit a negative sign on a digit in the middle of an expression", () => {
201 | const actions = [
202 | ["op-sub", "-"],
203 | ["digit-1", "-1"],
204 | ["digit-2", "-12"],
205 | ["op-div", "-12/"],
206 | ["op-sub", "-12/-"],
207 | ["digit-5", "-12/-5"],
208 | ["eq", "2"],
209 | ["clear", ""],
210 | ];
211 |
212 | for (const [op, expected] of actions) {
213 | buttons[op].simulate("click");
214 | expect(output.text()).toBe(expected);
215 | }
216 | });
217 | });
218 |
219 | describe('Division by zero', () => {
220 | it("should clear the display on division by zero", () => {
221 | const actions = [
222 | ["digit-3", "3"],
223 | ["op-div", "3/"],
224 | ["digit-0", "3/0"],
225 | ["eq", ""],
226 | ["digit-3", "3"],
227 | ["op-div", "3/"],
228 | ["op-sub", "3/-"],
229 | ["digit-0", "3/-0"],
230 | ["eq", ""],
231 | ];
232 |
233 | for (const [op, expected] of actions) {
234 | buttons[op].simulate("click");
235 | expect(output.text()).toBe(expected);
236 | }
237 | });
238 |
239 | it("should clear an expression on division by negative zero", () => {
240 | const actions = [
241 | ["digit-3", "3"],
242 | ["op-div", "3/"],
243 | ["op-sub", "3/-"],
244 | ["digit-0", "3/-0"],
245 | ["eq", ""],
246 | ];
247 |
248 | for (const [op, expected] of actions) {
249 | buttons[op].simulate("click");
250 | expect(output.text()).toBe(expected);
251 | }
252 | });
253 | });
254 |
255 | describe('Handling input requirements', () => {
256 | it('No button other than "op-sub" and digits should have an effect from a clear state', () => {
257 | const actions = [
258 | ["op-div", ""],
259 | ["op-add", ""],
260 | ["op-mul", ""],
261 | ["eq", ""],
262 | ["clear", ""],
263 | ];
264 |
265 | for (const [op, expected] of actions) {
266 | buttons[op].simulate("click");
267 | expect(output.text()).toBe(expected);
268 | }
269 | });
270 |
271 | it('Only one leading minus sign is permitted', () => {
272 | const actions = [
273 | ["op-sub", "-"],
274 | ["op-sub", "-"],
275 | ["clear", ""],
276 | ];
277 |
278 | for (const [op, expected] of actions) {
279 | buttons[op].simulate("click");
280 | expect(output.text()).toBe(expected);
281 | }
282 | });
283 |
284 | it('should be possible to use the result of the last equation to build a new one by pushing an operator button', () => {
285 | const actions = [
286 | ["digit-9", "9"],
287 | ["op-add", "9+"],
288 | ["digit-8", "9+8"],
289 | ["eq", "17"],
290 | ["op-sub", "17-"],
291 | ["digit-6", "17-6"],
292 | ["eq", "11"],
293 | ];
294 |
295 | for (const [op, expected] of actions) {
296 | buttons[op].simulate("click");
297 | expect(output.text()).toBe(expected);
298 | }
299 | });
300 |
301 | it('should be possible to clear the result of the last equation and begin a new one by pushing a digit button', () => {
302 | const actions = [
303 | ["digit-9", "9"],
304 | ["op-add", "9+"],
305 | ["digit-6", "9+6"],
306 | ["eq", "15"],
307 | ["digit-3", "3"],
308 | ["op-mul", "3*"],
309 | ["digit-1", "3*1"],
310 | ["eq", "3"],
311 | ];
312 |
313 | for (const [op, expected] of actions) {
314 | buttons[op].simulate("click");
315 | expect(output.text()).toBe(expected);
316 | }
317 | });
318 |
319 | it('should disallow leading zeroes', () => {
320 | const actions = [
321 | ["digit-0", "0"],
322 | ["digit-7", "7"],
323 | ["digit-0", "70"],
324 | ["op-mul", "70*"],
325 | ["digit-0", "70*0"],
326 | ["digit-6", "70*6"],
327 | ["eq", "420"],
328 | ];
329 |
330 | for (const [op, expected] of actions) {
331 | buttons[op].simulate("click");
332 | expect(output.text()).toBe(expected);
333 | }
334 | });
335 |
336 | it('should allow leading negative signs on zeroes', () => {
337 | const actions = [
338 | ["digit-0", "0"],
339 | ["digit-7", "7"],
340 | ["digit-0", "70"],
341 | ["op-mul", "70*"],
342 | ["op-sub", "70*-"],
343 | ["digit-0", "70*-0"],
344 | ["digit-6", "70*-6"],
345 | ["eq", "-420"],
346 | ["clear", ""],
347 | ];
348 |
349 | for (const [op, expected] of actions) {
350 | buttons[op].simulate("click");
351 | expect(output.text()).toBe(expected);
352 | }
353 | });
354 |
355 | it('should switch and append operators correctly', () => {
356 | const actions = [
357 | ["digit-6", "6"],
358 | ["op-div", "6/"],
359 | ["op-add", "6+"],
360 | ["op-mul", "6*"],
361 | ["op-add", "6+"],
362 | ["op-sub", "6-"],
363 | ["op-div", "6/"],
364 | ["op-sub", "6/-"],
365 | ["op-sub", "6/-"],
366 | ["op-add", "6/-"],
367 | ["op-div", "6/-"],
368 | ["op-div", "6/-"],
369 | ["digit-4", "6/-4"],
370 | ["op-mul", "6/-4*"],
371 | ["op-sub", "6/-4*-"],
372 | ["op-sub", "6/-4*-"],
373 | ["clear", ""],
374 | ];
375 |
376 | for (const [op, expected] of actions) {
377 | buttons[op].simulate("click");
378 | expect(output.text()).toBe(expected);
379 | }
380 | });
381 |
382 | it('should handle situations where "eq" has no effect', () => {
383 | const actions = [
384 | ["eq", ""],
385 | ["op-sub", "-"],
386 | ["eq", "-"],
387 | ["digit-1", "-1"],
388 | ["eq", "-1"],
389 | ["op-mul", "-1*"],
390 | ["eq", "-1*"],
391 | ["clear", ""],
392 | ];
393 |
394 | for (const [op, expected] of actions) {
395 | buttons[op].simulate("click");
396 | expect(output.text()).toBe(expected);
397 | }
398 | });
399 |
400 | it('should work on a complex sequence of inputs', () => {
401 | const actions = [
402 | ["op-sub", "-"],
403 | ["digit-8", "-8"],
404 | ["op-sub", "-8-"],
405 | ["digit-7", "-8-7"],
406 | ["digit-2", "-8-72"],
407 | ["digit-1", "-8-721"],
408 | ["digit-6", "-8-7216"],
409 | ["op-div", "-8-7216/"],
410 | ["op-sub", "-8-7216/-"],
411 | ["digit-3", "-8-7216/-3"],
412 | ["op-mul", "-8-7216/-3*"],
413 | ["op-sub", "-8-7216/-3*-"],
414 | ["digit-2", "-8-7216/-3*-2"],
415 | ["digit-4", "-8-7216/-3*-24"],
416 | ["op-mul", "-8-7216/-3*-24*"],
417 | ["digit-5", "-8-7216/-3*-24*5"],
418 | ["op-add", "-8-7216/-3*-24*5+"],
419 | ["digit-9", "-8-7216/-3*-24*5+9"],
420 | ["digit-9", "-8-7216/-3*-24*5+99"],
421 | ["digit-0", "-8-7216/-3*-24*5+990"],
422 | ["digit-0", "-8-7216/-3*-24*5+9900"],
423 | ["eq", "-278828"],
424 | ["clear", ""],
425 | ];
426 |
427 | for (const [op, expected] of actions) {
428 | buttons[op].simulate("click");
429 | expect(output.text()).toBe(expected);
430 | }
431 | });
432 | });
433 |
434 | it('should work on a bunch of zeroes', () => {
435 | const actions = [
436 | ["op-sub", "-"],
437 | ["op-sub", "-"],
438 | ["digit-0", "-0"],
439 | ["digit-0", "-0"],
440 | ["op-sub", "-0-"],
441 | ["op-add", "-0+"],
442 | ["op-sub", "-0-"],
443 | ["op-sub", "-0-"],
444 | ["digit-0", "-0-0"],
445 | ["digit-0", "-0-0"],
446 | ["op-sub", "-0-0-"],
447 | ["digit-0", "-0-0-0"],
448 | ["digit-0", "-0-0-0"],
449 | ["eq", "0"],
450 | ["clear", ""],
451 | ];
452 |
453 | for (const [op, expected] of actions) {
454 | buttons[op].simulate("click");
455 | expect(output.text()).toBe(expected);
456 | }
457 | });
458 | });
--------------------------------------------------------------------------------
/1/src/__test__/Calculator.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Enzyme, { mount, shallow } from "enzyme";
3 | import Adapter from "enzyme-adapter-react-16";
4 | Enzyme.configure({ adapter: new Adapter() });
5 | import Calculator from "../../src/Calculator"
6 |
7 | const buttonNames = [
8 | "op-add", "op-mul", "op-div", "op-sub", "clear", "eq",
9 | ...Array(10).fill().map((e, i) => `digit-${i}`),
10 | ];
11 | const getButtons = calc =>
12 | buttonNames.reduce((accumulator, btn) => {
13 | accumulator[btn] = calc.find("." + btn);
14 | return accumulator;
15 | }, {})
16 | ;
17 | let calc;
18 | let buttons;
19 | let output;
20 |
21 | describe('Calculator', () => {
22 | beforeEach(() => {
23 | calc = mount( );
24 | buttons = getButtons(calc);
25 | output = calc.find(".output");
26 | });
27 | afterEach(() => {
28 | calc.unmount();
29 | });
30 |
31 | describe('Output element', () => {
32 | it('should render an element with class name "output"', () => {
33 | expect(calc.find('.output').exists()).toBe(true);
34 | });
35 |
36 | it('should have an empty output display initially', () => {
37 | expect(calc.find(".output").text()).toBe("");
38 | });
39 | });
40 |
41 | describe('Buttons should be rendered and be clickable', () => {
42 | describe('Operator buttons', () => {
43 | for (const op of ["add", "sub", "mul", "div"]) {
44 | test(`should render operator button "op-${op}"`, () => {
45 | expect(calc.find(`.op-${op}`).exists()).toBe(true);
46 | });
47 | }
48 |
49 | it('should render equals button with class "eq"', () => {
50 | expect(calc.find(".eq").exists()).toBe(true);
51 | });
52 |
53 | it('should render clear button with class "clear"', () => {
54 | expect(calc.find(".clear").exists()).toBe(true);
55 | });
56 | });
57 |
58 | describe('Digit buttons', () => {
59 | for (let i = 0; i < 10; i++) {
60 | test(`button for digit ${i} should exist with class "digit-${i}"`, () => {
61 | expect(calc.find(`.digit-${i}`).exists()).toBe(true);
62 | });
63 |
64 | test(`should update output when "digit-${i}" button is clicked`, () => {
65 | calc.find(`.digit-${i}`).simulate("click");
66 | expect(calc.find(".output").text()).toBe("" + i);
67 | });
68 | }
69 | });
70 | });
71 |
72 | describe('Clearing the display', () => {
73 | it('should clear the display when the "clear" button is clicked', () => {
74 | const actions = [
75 | ["digit-9", "9"],
76 | ["clear", ""],
77 | ];
78 |
79 | for (const [op, expected] of actions) {
80 | buttons[op].simulate("click");
81 | expect(output.text()).toBe(expected);
82 | }
83 | });
84 | });
85 |
86 | describe('Simple operations', () => {
87 | it("should work on addition", () => {
88 | const actions = [
89 | ["digit-3", "3"],
90 | ["op-add", "3+"],
91 | ["digit-5", "3+5"],
92 | ["eq", "8"],
93 | ];
94 |
95 | for (const [op, expected] of actions) {
96 | buttons[op].simulate("click");
97 | expect(output.text()).toBe(expected);
98 | }
99 | });
100 |
101 | it("should work on subtraction", () => {
102 | const actions = [
103 | ["digit-3", "3"],
104 | ["op-sub", "3-"],
105 | ["digit-5", "3-5"],
106 | ["eq", "-2"],
107 | ];
108 |
109 | for (const [op, expected] of actions) {
110 | buttons[op].simulate("click");
111 | expect(output.text()).toBe(expected);
112 | }
113 | });
114 |
115 | it("should work on multiplication", () => {
116 | const actions = [
117 | ["digit-3", "3"],
118 | ["op-mul", "3*"],
119 | ["digit-5", "3*5"],
120 | ["eq", "15"],
121 | ];
122 |
123 | for (const [op, expected] of actions) {
124 | buttons[op].simulate("click");
125 | expect(output.text()).toBe(expected);
126 | }
127 | });
128 |
129 | it("should work on integer division", () => {
130 | const actions = [
131 | ["digit-3", "3"],
132 | ["op-div", "3/"],
133 | ["digit-5", "3/5"],
134 | ["eq", "0"],
135 | ];
136 |
137 | for (const [op, expected] of actions) {
138 | buttons[op].simulate("click");
139 | expect(output.text()).toBe(expected);
140 | }
141 | });
142 |
143 | it("should work on an expression containing multi-digit numbers", () => {
144 | const actions = [
145 | ["digit-6", "6"],
146 | ["digit-3", "63"],
147 | ["op-div", "63/"],
148 | ["digit-1", "63/1"],
149 | ["digit-5", "63/15"],
150 | ["eq", "4"],
151 | ];
152 |
153 | for (const [op, expected] of actions) {
154 | buttons[op].simulate("click");
155 | expect(output.text()).toBe(expected);
156 | }
157 | });
158 |
159 | it("should work on an expression with multiple operators", () => {
160 | const actions = [
161 | ["digit-2", "2"],
162 | ["digit-1", "21"],
163 | ["digit-3", "213"],
164 | ["op-sub", "213-"],
165 | ["digit-2", "213-2"],
166 | ["digit-0", "213-20"],
167 | ["digit-0", "213-200"],
168 | ["op-add", "213-200+"],
169 | ["digit-1", "213-200+1"],
170 | ["digit-0", "213-200+10"],
171 | ["eq", "23"],
172 | ["clear", ""],
173 | ];
174 |
175 | for (const [op, expected] of actions) {
176 | buttons[op].simulate("click");
177 | expect(output.text()).toBe(expected);
178 | }
179 | });
180 | });
181 |
182 | describe('Negative numbers', () => {
183 | it("should permit a leading negative sign", () => {
184 | const actions = [
185 | ["op-sub", "-"],
186 | ["digit-1", "-1"],
187 | ["digit-2", "-12"],
188 | ["op-div", "-12/"],
189 | ["digit-5", "-12/5"],
190 | ["eq", "-3"],
191 | ["clear", ""],
192 | ];
193 |
194 | for (const [op, expected] of actions) {
195 | buttons[op].simulate("click");
196 | expect(output.text()).toBe(expected);
197 | }
198 | });
199 |
200 | it("should permit a negative sign on a digit in the middle of an expression", () => {
201 | const actions = [
202 | ["op-sub", "-"],
203 | ["digit-1", "-1"],
204 | ["digit-2", "-12"],
205 | ["op-div", "-12/"],
206 | ["op-sub", "-12/-"],
207 | ["digit-5", "-12/-5"],
208 | ["eq", "2"],
209 | ["clear", ""],
210 | ];
211 |
212 | for (const [op, expected] of actions) {
213 | buttons[op].simulate("click");
214 | expect(output.text()).toBe(expected);
215 | }
216 | });
217 | });
218 |
219 | describe('Division by zero', () => {
220 | it("should clear the display on division by zero", () => {
221 | const actions = [
222 | ["digit-3", "3"],
223 | ["op-div", "3/"],
224 | ["digit-0", "3/0"],
225 | ["eq", ""],
226 | ["digit-3", "3"],
227 | ["op-div", "3/"],
228 | ["op-sub", "3/-"],
229 | ["digit-0", "3/-0"],
230 | ["eq", ""],
231 | ];
232 |
233 | for (const [op, expected] of actions) {
234 | buttons[op].simulate("click");
235 | expect(output.text()).toBe(expected);
236 | }
237 | });
238 |
239 | it("should clear an expression on division by negative zero", () => {
240 | const actions = [
241 | ["digit-3", "3"],
242 | ["op-div", "3/"],
243 | ["op-sub", "3/-"],
244 | ["digit-0", "3/-0"],
245 | ["eq", ""],
246 | ];
247 |
248 | for (const [op, expected] of actions) {
249 | buttons[op].simulate("click");
250 | expect(output.text()).toBe(expected);
251 | }
252 | });
253 | });
254 |
255 | describe('Handling input requirements', () => {
256 | it('No button other than "op-sub" and digits should have an effect from a clear state', () => {
257 | const actions = [
258 | ["op-div", ""],
259 | ["op-add", ""],
260 | ["op-mul", ""],
261 | ["eq", ""],
262 | ["clear", ""],
263 | ];
264 |
265 | for (const [op, expected] of actions) {
266 | buttons[op].simulate("click");
267 | expect(output.text()).toBe(expected);
268 | }
269 | });
270 |
271 | it('Only one leading minus sign is permitted', () => {
272 | const actions = [
273 | ["op-sub", "-"],
274 | ["op-sub", "-"],
275 | ["clear", ""],
276 | ];
277 |
278 | for (const [op, expected] of actions) {
279 | buttons[op].simulate("click");
280 | expect(output.text()).toBe(expected);
281 | }
282 | });
283 |
284 | it('should be possible to use the result of the last equation to build a new one by pushing an operator button', () => {
285 | const actions = [
286 | ["digit-9", "9"],
287 | ["op-add", "9+"],
288 | ["digit-8", "9+8"],
289 | ["eq", "17"],
290 | ["op-sub", "17-"],
291 | ["digit-6", "17-6"],
292 | ["eq", "11"],
293 | ];
294 |
295 | for (const [op, expected] of actions) {
296 | buttons[op].simulate("click");
297 | expect(output.text()).toBe(expected);
298 | }
299 | });
300 |
301 | it('should be possible to clear the result of the last equation and begin a new one by pushing a digit button', () => {
302 | const actions = [
303 | ["digit-9", "9"],
304 | ["op-add", "9+"],
305 | ["digit-6", "9+6"],
306 | ["eq", "15"],
307 | ["digit-3", "3"],
308 | ["op-mul", "3*"],
309 | ["digit-1", "3*1"],
310 | ["eq", "3"],
311 | ];
312 |
313 | for (const [op, expected] of actions) {
314 | buttons[op].simulate("click");
315 | expect(output.text()).toBe(expected);
316 | }
317 | });
318 |
319 | it('should disallow leading zeroes', () => {
320 | const actions = [
321 | ["digit-0", "0"],
322 | ["digit-7", "7"],
323 | ["digit-0", "70"],
324 | ["op-mul", "70*"],
325 | ["digit-0", "70*0"],
326 | ["digit-6", "70*6"],
327 | ["eq", "420"],
328 | ];
329 |
330 | for (const [op, expected] of actions) {
331 | buttons[op].simulate("click");
332 | expect(output.text()).toBe(expected);
333 | }
334 | });
335 |
336 | it('should allow leading negative signs on zeroes', () => {
337 | const actions = [
338 | ["digit-0", "0"],
339 | ["digit-7", "7"],
340 | ["digit-0", "70"],
341 | ["op-mul", "70*"],
342 | ["op-sub", "70*-"],
343 | ["digit-0", "70*-0"],
344 | ["digit-6", "70*-6"],
345 | ["eq", "-420"],
346 | ["clear", ""],
347 | ];
348 |
349 | for (const [op, expected] of actions) {
350 | buttons[op].simulate("click");
351 | expect(output.text()).toBe(expected);
352 | }
353 | });
354 |
355 | it('should switch and append operators correctly', () => {
356 | const actions = [
357 | ["digit-6", "6"],
358 | ["op-div", "6/"],
359 | ["op-add", "6+"],
360 | ["op-mul", "6*"],
361 | ["op-add", "6+"],
362 | ["op-sub", "6-"],
363 | ["op-div", "6/"],
364 | ["op-sub", "6/-"],
365 | ["op-sub", "6/-"],
366 | ["op-add", "6/-"],
367 | ["op-div", "6/-"],
368 | ["op-div", "6/-"],
369 | ["digit-4", "6/-4"],
370 | ["op-mul", "6/-4*"],
371 | ["op-sub", "6/-4*-"],
372 | ["op-sub", "6/-4*-"],
373 | ["clear", ""],
374 | ];
375 |
376 | for (const [op, expected] of actions) {
377 | buttons[op].simulate("click");
378 | expect(output.text()).toBe(expected);
379 | }
380 | });
381 |
382 | it('should handle situations where "eq" has no effect', () => {
383 | const actions = [
384 | ["eq", ""],
385 | ["op-sub", "-"],
386 | ["eq", "-"],
387 | ["digit-1", "-1"],
388 | ["eq", "-1"],
389 | ["op-mul", "-1*"],
390 | ["eq", "-1*"],
391 | ["clear", ""],
392 | ];
393 |
394 | for (const [op, expected] of actions) {
395 | buttons[op].simulate("click");
396 | expect(output.text()).toBe(expected);
397 | }
398 | });
399 |
400 | it('should work on a complex sequence of inputs', () => {
401 | const actions = [
402 | ["op-sub", "-"],
403 | ["digit-8", "-8"],
404 | ["op-sub", "-8-"],
405 | ["digit-7", "-8-7"],
406 | ["digit-2", "-8-72"],
407 | ["digit-1", "-8-721"],
408 | ["digit-6", "-8-7216"],
409 | ["op-div", "-8-7216/"],
410 | ["op-sub", "-8-7216/-"],
411 | ["digit-3", "-8-7216/-3"],
412 | ["op-mul", "-8-7216/-3*"],
413 | ["op-sub", "-8-7216/-3*-"],
414 | ["digit-2", "-8-7216/-3*-2"],
415 | ["digit-4", "-8-7216/-3*-24"],
416 | ["op-mul", "-8-7216/-3*-24*"],
417 | ["digit-5", "-8-7216/-3*-24*5"],
418 | ["op-add", "-8-7216/-3*-24*5+"],
419 | ["digit-9", "-8-7216/-3*-24*5+9"],
420 | ["digit-9", "-8-7216/-3*-24*5+99"],
421 | ["digit-0", "-8-7216/-3*-24*5+990"],
422 | ["digit-0", "-8-7216/-3*-24*5+9900"],
423 | ["eq", "-278828"],
424 | ["clear", ""],
425 | ];
426 |
427 | for (const [op, expected] of actions) {
428 | buttons[op].simulate("click");
429 | expect(output.text()).toBe(expected);
430 | }
431 | });
432 | });
433 |
434 | it('should work on a bunch of zeroes', () => {
435 | const actions = [
436 | ["op-sub", "-"],
437 | ["op-sub", "-"],
438 | ["digit-0", "-0"],
439 | ["digit-0", "-0"],
440 | ["op-sub", "-0-"],
441 | ["op-add", "-0+"],
442 | ["op-sub", "-0-"],
443 | ["op-sub", "-0-"],
444 | ["digit-0", "-0-0"],
445 | ["digit-0", "-0-0"],
446 | ["op-sub", "-0-0-"],
447 | ["digit-0", "-0-0-0"],
448 | ["digit-0", "-0-0-0"],
449 | ["eq", "0"],
450 | ["clear", ""],
451 | ];
452 |
453 | for (const [op, expected] of actions) {
454 | buttons[op].simulate("click");
455 | expect(output.text()).toBe(expected);
456 | }
457 | });
458 | });
--------------------------------------------------------------------------------