From 7310400d20237c8275e5ee6a7ae65f067878aa3b Mon Sep 17 00:00:00 2001 From: Kevin Barabash Date: Tue, 26 Sep 2023 12:17:14 -0400 Subject: [PATCH 01/11] Upload merged coverage report to Codecov (#738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: While Codecov supports uploading multiple reports, the experience isn't great. In particular, if only one report uploads Codecov will show a report with the coverage dropping because it only has coverage from half of the reports. This PR avoids the issue by merging the reports ourselves. We still run jest and cypress in parallel. We use artifacts to pass the reports to another job which merges them and uploads them. Issue: None ## Test plan: - we should see an increase in the coverage Author: kevinbarabash Reviewers: kevinbarabash, jaredly, jeremywiebe Required Reviewers: Approved By: jaredly, jeremywiebe Checks: ⌛ Publish npm snapshot (ubuntu-latest, 16.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 16.x), ✅ Extract i18n strings (ubuntu-latest, 16.x), ⌛ Cypress (ubuntu-latest, 16.x), ⌛ Jest Coverage (ubuntu-latest, 16.x), ⌛ Publish Storybook to Chromatic (ubuntu-latest, 16.x), ⌛ Check builds for changes in size (ubuntu-latest, 16.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 16.x), ✅ gerald Pull Request URL: https://github.com/Khan/perseus/pull/738 --- .changeset/small-pears-admire.md | 2 ++ .codecov.yml | 14 ++------- .github/workflows/node-ci.yml | 49 +++++++++++++++++++++++++++++++- .nycrc.json | 14 +++++++++ 4 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 .changeset/small-pears-admire.md create mode 100644 .nycrc.json diff --git a/.changeset/small-pears-admire.md b/.changeset/small-pears-admire.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/small-pears-admire.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.codecov.yml b/.codecov.yml index 14180321c5..272ef57fb6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -3,20 +3,12 @@ coverage: round: down range: "70...100" - status: - project: - default: on - patch: - default: off - changes: - default: off - comment: layout: "diff, reach, files, footer" behavior: default - require_changes: no - require_base: no - require_head: yes + require_changes: false + require_base: false + require_head: true # https://docs.codecov.com/docs/ignoring-paths ignore: diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 1d341d0122..3c27454566 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -136,6 +136,14 @@ jobs: - name: Run test with coverage run: yarn cypress:ci + # Upload coverage report as an GitHub artifact so that it can be used + # later in upload_coverage. + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: cypress-coverage + path: ./.nyc_output/out.json + coverage: name: Jest Coverage runs-on: ${{ matrix.os }} @@ -154,11 +162,50 @@ jobs: - name: Jest with coverage run: yarn coverage + # Upload coverage report as an GitHub artifact so that it can be used + # later in upload_coverage. + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: jest-coverage + path: ./coverage/coverage-final.json + + upload_coverage: + name: Upload Coverage + runs-on: ubuntu-latest + needs: [cypress, coverage] + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} & Install & cache node_modules + uses: Khan/actions@shared-node-cache-v0 + with: + node-version: ${{ matrix.node-version }} + + - name: Download Jest Coverage + uses: actions/download-artifact@v2 + with: + name: jest-coverage + # path to decompress the artifact into, decompressed file + # will be ./coverage-final.json + path: ./ + + - name: Download Cypress Coverage + uses: actions/download-artifact@v2 + with: + name: cypress-coverage + # path to decompress the artifact into, decompressed file + # will be ./out.json + path: ./ + + # Upload both coverage files at once. This avoids issues where it Codecov + # shows the results from only one of the reports which would make it appear + # as though coverage dropped a lot. - name: Upload Coverage uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/coverage-final.json + files: ./coverage-final.json,./out.json check_builds: name: Check builds for changes in size diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 0000000000..8eaba5538e --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,14 @@ +{ + "all": true, + "include": [ + "packages/*/src/**/*.js", + "packages/*/src/**/*.jsx", + "packages/*/src/**/*.ts", + "packages/*/src/**/*.tsx" + ], + "exclude": [ + "**/*.stories.tsx", + "**/*.test.ts", + "**/*.test.tsx" + ] +} \ No newline at end of file From 3b19a1bfbe34873722daa20a8814b159117a0a42 Mon Sep 17 00:00:00 2001 From: Sarah Third Date: Tue, 26 Sep 2023 09:39:31 -0700 Subject: [PATCH 02/11] Itty bitty little fix (#743) --- .changeset/wild-suns-explain.md | 5 +++++ packages/math-input/src/components/keypad/mobile-keypad.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/wild-suns-explain.md diff --git a/.changeset/wild-suns-explain.md b/.changeset/wild-suns-explain.md new file mode 100644 index 0000000000..2b58416e37 --- /dev/null +++ b/.changeset/wild-suns-explain.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/math-input": minor +--- + +Ensured that we're properly calling componentWillUnmount diff --git a/packages/math-input/src/components/keypad/mobile-keypad.tsx b/packages/math-input/src/components/keypad/mobile-keypad.tsx index 8d179a76e5..dbf8b5b2c3 100644 --- a/packages/math-input/src/components/keypad/mobile-keypad.tsx +++ b/packages/math-input/src/components/keypad/mobile-keypad.tsx @@ -81,7 +81,7 @@ class MobileKeypad extends React.Component implements KeypadAPI { } } - componentWillUnMount() { + componentWillUnmount() { window.removeEventListener("resize", this._throttleResizeHandler); window.removeEventListener( "orientationchange", From 7e2ae0ef384aced0824ce20090d7cc0a606b6e59 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 26 Sep 2023 11:39:55 -0500 Subject: [PATCH 03/11] fix FRAC in fraction keypad (#744) * fix FRAC in fraction keypad * unfocus test * changeset --- .changeset/nervous-dingos-repair.md | 6 + .../components/__tests__/integration.test.tsx | 128 ++++++++++++++++-- .../keypad/keypad-pages/fractions-page.tsx | 2 +- .../__tests__/expression-mobile.test.tsx | 8 +- 4 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 .changeset/nervous-dingos-repair.md diff --git a/.changeset/nervous-dingos-repair.md b/.changeset/nervous-dingos-repair.md new file mode 100644 index 0000000000..47c8e95930 --- /dev/null +++ b/.changeset/nervous-dingos-repair.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/math-input": patch +"@khanacademy/perseus": patch +--- + +Bugfix for fraction button in v2 fraction keypad diff --git a/packages/math-input/src/components/__tests__/integration.test.tsx b/packages/math-input/src/components/__tests__/integration.test.tsx index 856cdaa783..13047d9e79 100644 --- a/packages/math-input/src/components/__tests__/integration.test.tsx +++ b/packages/math-input/src/components/__tests__/integration.test.tsx @@ -1,18 +1,29 @@ import "@testing-library/jest-dom"; -import {screen, render, fireEvent, within} from "@testing-library/react"; +import { + screen, + render, + fireEvent, + within, + waitFor, +} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import MathQuill from "mathquill"; import React, {useState} from "react"; +import {KeypadType} from "../../enums"; import MathInput from "../input/math-input"; import KeypadContext from "../keypad-context"; import KeypadSwitch from "../keypad-switch"; -import type {KeypadAPI} from "../../types"; +import type {KeypadAPI, KeypadConfiguration} from "../../types"; const MQ = MathQuill.getInterface(2); -function InputWithContext() { +const defaultConfiguration: KeypadConfiguration = { + keypadType: KeypadType.FRACTION, +}; + +function InputWithContext({keypadConfiguration}) { const [value, setValue] = useState(""); return ( @@ -27,7 +38,12 @@ function InputWithContext() { cb(); }} onFocus={() => { - keypadElement?.activate(); + keypadElement?.configure( + keypadConfiguration, + () => { + keypadElement?.activate(); + }, + ); }} onBlur={() => { keypadElement?.dismiss(); @@ -56,7 +72,7 @@ function KeypadWithContext() { ); } -function ConnectedMathInput() { +function ConnectedMathInput({keypadConfiguration = defaultConfiguration}) { const [keypadElement, setKeypadElement] = useState(); const [renderer, setRenderer] = useState(null); const [scrollableElement, setScrollableElement] = @@ -73,7 +89,7 @@ function ConnectedMathInput() { scrollableElement, }} > - + ); @@ -107,10 +123,14 @@ describe("math input integration", () => { fireEvent.touchStart(input); + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + expect(screen.getByRole("button", {name: "1"})).toBeVisible(); }); - it("updates input when using keypad", () => { + it("updates input when using keypad", async () => { render(); const input = screen.getByLabelText( @@ -118,6 +138,11 @@ describe("math input integration", () => { ); fireEvent.touchStart(input); + + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + userEvent.click(screen.getByRole("button", {name: "1"})); // MathQuill is problematic, @@ -130,7 +155,7 @@ describe("math input integration", () => { expect(span1).toBeVisible(); }); - it("updates input when pressing many numbers", () => { + it("updates input when pressing many numbers", async () => { render(); const input = screen.getByLabelText( @@ -139,6 +164,10 @@ describe("math input integration", () => { fireEvent.touchStart(input); + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + const testNumbers = [8, 6, 7, 5, 3, 0, 9]; testNumbers.forEach((num) => { userEvent.click(screen.getByRole("button", {name: `${num}`})); @@ -153,7 +182,7 @@ describe("math input integration", () => { expect(mathquillInstance.latex()).toBe("8675309"); }); - it("can handle symbols", () => { + it("can handle symbols", async () => { render(); const input = screen.getByLabelText( @@ -162,6 +191,10 @@ describe("math input integration", () => { fireEvent.touchStart(input); + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + userEvent.click(screen.getByRole("button", {name: "4"})); userEvent.click(screen.getByRole("button", {name: "2"})); userEvent.click(screen.getByRole("button", {name: "Percent"})); @@ -174,4 +207,81 @@ describe("math input integration", () => { expect(mathquillInstance.latex()).toBe("42\\%"); }); + + it("handles fractions correctly in expression", async () => { + const keypadConfiguration = { + keypadType: KeypadType.EXPRESSION, + }; + render( + , + ); + + const input = screen.getByLabelText( + "Math input box Tap with one or two fingers to open keyboard", + ); + + fireEvent.touchStart(input); + + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + + userEvent.click(screen.getByRole("button", {name: "1"})); + userEvent.click( + screen.getByRole("button", { + name: "Fraction, excluding the current expression", + }), + ); + userEvent.click(screen.getByRole("button", {name: "4"})); + userEvent.click( + screen.getByRole("button", { + name: "Navigate right out of the numerator and into the denominator", + }), + ); + userEvent.click(screen.getByRole("button", {name: "2"})); + + // MathQuill is problematic, + // this is how to get the value of the input directly from MQ + const mathquillInstance = + // eslint-disable-next-line testing-library/no-node-access + MQ(document.getElementsByClassName("mq-editable-field")[0]); + + expect(mathquillInstance.latex()).toBe("1\\frac{4}{2}"); + }); + + it("handles fractions correctly in fraction", async () => { + render(); + + const input = screen.getByLabelText( + "Math input box Tap with one or two fingers to open keyboard", + ); + + fireEvent.touchStart(input); + + await waitFor(() => { + expect(screen.getByRole("button", {name: "4"})).toBeVisible(); + }); + + userEvent.click(screen.getByRole("button", {name: "1"})); + userEvent.click( + screen.getByRole("button", { + name: "Fraction, excluding the current expression", + }), + ); + userEvent.click(screen.getByRole("button", {name: "4"})); + userEvent.click( + screen.getByRole("button", { + name: "Navigate right out of the numerator and into the denominator", + }), + ); + userEvent.click(screen.getByRole("button", {name: "2"})); + + // MathQuill is problematic, + // this is how to get the value of the input directly from MQ + const mathquillInstance = + // eslint-disable-next-line testing-library/no-node-access + MQ(document.getElementsByClassName("mq-editable-field")[0]); + + expect(mathquillInstance.latex()).toBe("1\\frac{4}{2}"); + }); }); diff --git a/packages/math-input/src/components/keypad/keypad-pages/fractions-page.tsx b/packages/math-input/src/components/keypad/keypad-pages/fractions-page.tsx index eec3afd122..d818ceec5f 100644 --- a/packages/math-input/src/components/keypad/keypad-pages/fractions-page.tsx +++ b/packages/math-input/src/components/keypad/keypad-pages/fractions-page.tsx @@ -101,7 +101,7 @@ export default function FractionsPage(props: Props) { secondary /> {({keypadElement}) => { @@ -37,7 +37,7 @@ function RendererWithContext() { customKeypad: true, useV2Keypad: true, }} - item={expressionItem2} + item={item} problemNum={0} reviewMode={false} dependencies={testDependenciesV2} @@ -66,7 +66,7 @@ function KeypadWithContext() { ); } -function ConnectedRenderer() { +function ConnectedRenderer({item = expressionItem2}) { const [keypadElement, setKeypadElement] = React.useState(); const [renderer, setRenderer] = React.useState(null); @@ -85,7 +85,7 @@ function ConnectedRenderer() { scrollableElement, }} > - + From 8359a9e60c6ee515c8adbdcca0a1ff1817758ea9 Mon Sep 17 00:00:00 2001 From: Khan Actions Bot <56267880+khan-actions-bot@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:57:25 -0400 Subject: [PATCH 04/11] Version Packages (#742) Co-authored-by: github-actions[bot] --- .changeset/few-teachers-fry.md | 2 -- .changeset/metal-suits-cheat.md | 2 -- .changeset/nasty-beans-hang.md | 2 -- .changeset/nervous-dingos-repair.md | 6 ------ .changeset/new-flowers-study.md | 6 ------ .changeset/small-pears-admire.md | 2 -- .changeset/wild-suns-explain.md | 5 ----- .changeset/yellow-needles-cover.md | 2 -- packages/math-input/CHANGELOG.md | 11 +++++++++++ packages/math-input/package.json | 2 +- packages/perseus-editor/CHANGELOG.md | 8 ++++++++ packages/perseus-editor/package.json | 4 ++-- packages/perseus/CHANGELOG.md | 11 +++++++++++ packages/perseus/package.json | 4 ++-- 14 files changed, 35 insertions(+), 32 deletions(-) delete mode 100644 .changeset/few-teachers-fry.md delete mode 100644 .changeset/metal-suits-cheat.md delete mode 100644 .changeset/nasty-beans-hang.md delete mode 100644 .changeset/nervous-dingos-repair.md delete mode 100644 .changeset/new-flowers-study.md delete mode 100644 .changeset/small-pears-admire.md delete mode 100644 .changeset/wild-suns-explain.md delete mode 100644 .changeset/yellow-needles-cover.md diff --git a/.changeset/few-teachers-fry.md b/.changeset/few-teachers-fry.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/few-teachers-fry.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/metal-suits-cheat.md b/.changeset/metal-suits-cheat.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/metal-suits-cheat.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/nasty-beans-hang.md b/.changeset/nasty-beans-hang.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/nasty-beans-hang.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/nervous-dingos-repair.md b/.changeset/nervous-dingos-repair.md deleted file mode 100644 index 47c8e95930..0000000000 --- a/.changeset/nervous-dingos-repair.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@khanacademy/math-input": patch -"@khanacademy/perseus": patch ---- - -Bugfix for fraction button in v2 fraction keypad diff --git a/.changeset/new-flowers-study.md b/.changeset/new-flowers-study.md deleted file mode 100644 index 964f73ecf0..0000000000 --- a/.changeset/new-flowers-study.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@khanacademy/math-input": patch -"@khanacademy/perseus": patch ---- - -Add tests for mobile MathInput diff --git a/.changeset/small-pears-admire.md b/.changeset/small-pears-admire.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/small-pears-admire.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/wild-suns-explain.md b/.changeset/wild-suns-explain.md deleted file mode 100644 index 2b58416e37..0000000000 --- a/.changeset/wild-suns-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@khanacademy/math-input": minor ---- - -Ensured that we're properly calling componentWillUnmount diff --git a/.changeset/yellow-needles-cover.md b/.changeset/yellow-needles-cover.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/yellow-needles-cover.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/packages/math-input/CHANGELOG.md b/packages/math-input/CHANGELOG.md index 8ef98fdd98..467ed2384d 100644 --- a/packages/math-input/CHANGELOG.md +++ b/packages/math-input/CHANGELOG.md @@ -1,5 +1,16 @@ # @khanacademy/math-input +## 13.1.0 + +### Minor Changes + +- 3b19a1bf: Ensured that we're properly calling componentWillUnmount + +### Patch Changes + +- 7e2ae0ef: Bugfix for fraction button in v2 fraction keypad +- 1dc460c7: Add tests for mobile MathInput + ## 13.0.0 ### Major Changes diff --git a/packages/math-input/package.json b/packages/math-input/package.json index 7ab361785b..1a25bf8233 100644 --- a/packages/math-input/package.json +++ b/packages/math-input/package.json @@ -3,7 +3,7 @@ "description": "Khan Academy's new expression editor for the mobile web.", "author": "Khan Academy", "license": "MIT", - "version": "13.0.0", + "version": "13.1.0", "publishConfig": { "access": "public" }, diff --git a/packages/perseus-editor/CHANGELOG.md b/packages/perseus-editor/CHANGELOG.md index 16f5a2d380..9c5ce9e019 100644 --- a/packages/perseus-editor/CHANGELOG.md +++ b/packages/perseus-editor/CHANGELOG.md @@ -1,5 +1,13 @@ # @khanacademy/perseus-editor +## 2.7.5 + +### Patch Changes + +- Updated dependencies [7e2ae0ef] +- Updated dependencies [1dc460c7] + - @khanacademy/perseus@11.5.1 + ## 2.7.4 ### Patch Changes diff --git a/packages/perseus-editor/package.json b/packages/perseus-editor/package.json index 57fc2248ae..f1a4be7a21 100644 --- a/packages/perseus-editor/package.json +++ b/packages/perseus-editor/package.json @@ -3,7 +3,7 @@ "description": "Perseus editors", "author": "Khan Academy", "license": "MIT", - "version": "2.7.4", + "version": "2.7.5", "publishConfig": { "access": "public" }, @@ -24,7 +24,7 @@ "dependencies": { "@khanacademy/kas": "^0.3.1", "@khanacademy/kmath": "^0.1.2", - "@khanacademy/perseus": "^11.5.0" + "@khanacademy/perseus": "^11.5.1" }, "devDependencies": { "@khanacademy/wonder-blocks-button": "^4.1.3", diff --git a/packages/perseus/CHANGELOG.md b/packages/perseus/CHANGELOG.md index 727bbf4738..60a3c74aa6 100644 --- a/packages/perseus/CHANGELOG.md +++ b/packages/perseus/CHANGELOG.md @@ -1,5 +1,16 @@ # @khanacademy/perseus +## 11.5.1 + +### Patch Changes + +- 7e2ae0ef: Bugfix for fraction button in v2 fraction keypad +- 1dc460c7: Add tests for mobile MathInput +- Updated dependencies [7e2ae0ef] +- Updated dependencies [1dc460c7] +- Updated dependencies [3b19a1bf] + - @khanacademy/math-input@13.1.0 + ## 11.5.0 ### Minor Changes diff --git a/packages/perseus/package.json b/packages/perseus/package.json index a593fd9fd7..57941173f3 100644 --- a/packages/perseus/package.json +++ b/packages/perseus/package.json @@ -3,7 +3,7 @@ "description": "Core Perseus API (includes renderers and widgets)", "author": "Khan Academy", "license": "MIT", - "version": "11.5.0", + "version": "11.5.1", "publishConfig": { "access": "public" }, @@ -24,7 +24,7 @@ "dependencies": { "@khanacademy/kas": "^0.3.1", "@khanacademy/kmath": "^0.1.2", - "@khanacademy/math-input": "^13.0.0", + "@khanacademy/math-input": "^13.1.0", "@khanacademy/perseus-core": "1.1.0", "@khanacademy/perseus-linter": "^0.3.4", "@khanacademy/pure-markdown": "^0.2.6", From 0761377a4b614a59f4b3cb1d3289ddec0a6161b7 Mon Sep 17 00:00:00 2001 From: Sarah Third Date: Tue, 3 Oct 2023 13:55:09 -0700 Subject: [PATCH 05/11] Ensured that we're still validating against pi when it is provided in the answerForms. (#750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: This PR fixes a bug where we were no longer validating against pi when `answer.strict=false` when pi was provided in the answerForms. It looks like this is a side effect that came about with a more recent bug fix that exposed a hard-to-find historical bug. Previously we were always validating against pi by default, but we changed this after having issue with several questions being incorrectly tagged as approximations of pi. I updated tests to make sure that we're still validating against pi when these conditions are met. Issue: LC-1331 ## Test plan: - new test Author: SonicScrewdriver Reviewers: jeremywiebe, handeyeco Required Reviewers: Approved By: jeremywiebe Checks: ✅ codecov/project, ✅ codecov/patch, ✅ Upload Coverage, ✅ Publish npm snapshot (ubuntu-latest, 16.x), ✅ Cypress (ubuntu-latest, 16.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 16.x), ✅ Extract i18n strings (ubuntu-latest, 16.x), ✅ Jest Coverage (ubuntu-latest, 16.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 16.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 16.x), ✅ gerald, ✅ Check builds for changes in size (ubuntu-latest, 16.x) Pull Request URL: https://github.com/Khan/perseus/pull/750 --- .changeset/eleven-jokes-wait.md | 5 +++ packages/perseus/src/perseus-types.ts | 3 +- .../widgets/__tests__/numeric-input.test.ts | 35 +++++++++++++++++++ .../perseus/src/widgets/numeric-input.tsx | 32 +++++++++++------ 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 .changeset/eleven-jokes-wait.md diff --git a/.changeset/eleven-jokes-wait.md b/.changeset/eleven-jokes-wait.md new file mode 100644 index 0000000000..a291a4109a --- /dev/null +++ b/.changeset/eleven-jokes-wait.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +Ensured we're still validating against pi when strict is set to false and pi is in the answerforms. diff --git a/packages/perseus/src/perseus-types.ts b/packages/perseus/src/perseus-types.ts index 17315aa37d..8465a7e04d 100644 --- a/packages/perseus/src/perseus-types.ts +++ b/packages/perseus/src/perseus-types.ts @@ -762,7 +762,8 @@ export type PerseusNumericInputAnswer = { // The forms available for this answer. Options: "integer, ""decimal", "proper", "improper", "mixed", or "pi" // NOTE: perseus_data.go says this is required even though it isn't necessary. answerForms?: ReadonlyArray; - // Whether the answerForms should be strictly matched + // Whether we should check the answer strictly against the the configured answerForms (strict = true) + // or include the set of default answerForms (strict = false). strict: boolean; // A range of error +/- the value // NOTE: perseus_data.go says this is non-nullable even though we handle null values. diff --git a/packages/perseus/src/widgets/__tests__/numeric-input.test.ts b/packages/perseus/src/widgets/__tests__/numeric-input.test.ts index e58e16c06a..b45d96b5be 100644 --- a/packages/perseus/src/widgets/__tests__/numeric-input.test.ts +++ b/packages/perseus/src/widgets/__tests__/numeric-input.test.ts @@ -250,6 +250,41 @@ describe("static function validate", () => { expect(score.message?.includes("pi")).toBeFalsy(); }); + it("still validates against pi if provided in answerForms", () => { + const rubric: Rubric = { + answers: [ + { + maxError: null, + message: "", + simplify: "required", + status: "correct", + strict: false, + value: 311.01767270538954, + answerForms: ["pi"], + }, + ], + labelText: "", + size: "normal", + static: false, + coefficient: false, + }; + + const userInput = { + currentValue: "99 pi", + } as const; + + const score = NumericInput.validate(userInput, rubric); + + expect(score).toMatchInlineSnapshot(` + { + "earned": 1, + "message": null, + "total": 1, + "type": "points", + } + `); + }); + it("with a strict answer", () => { const rubric: Rubric = { answers: [ diff --git a/packages/perseus/src/widgets/numeric-input.tsx b/packages/perseus/src/widgets/numeric-input.tsx index 81427aa479..49749bcb4d 100644 --- a/packages/perseus/src/widgets/numeric-input.tsx +++ b/packages/perseus/src/widgets/numeric-input.tsx @@ -15,6 +15,7 @@ import type { PerseusNumericInputAnswer, PerseusNumericInputWidgetOptions, PerseusNumericInputAnswerForm, + MathFormat, } from "../perseus-types"; import type { FocusPath, @@ -25,7 +26,11 @@ import type { const ParseTex = TexWrangler.parseTex; -const answerFormButtons = [ +const answerFormButtons: ReadonlyArray<{ + title: string; + value: MathFormat; + content: string; +}> = [ {title: "Integers", value: "integer", content: "6"}, {title: "Decimals", value: "decimal", content: "0.75"}, {title: "Proper fractions", value: "proper", content: "\u2157"}, @@ -149,8 +154,8 @@ export class NumericInput extends React.Component { return answerStrings[0]; } - static validate(useInput: UserInput, rubric: Rubric): PerseusScore { - const allAnswerForms = answerFormButtons + static validate(userInput: UserInput, rubric: Rubric): PerseusScore { + const defaultAnswerForms = answerFormButtons .map((e) => e["value"]) // Don't default to validating the answer as a pi answer // if answerForm isn't set on the answer @@ -159,6 +164,18 @@ export class NumericInput extends React.Component { const createValidator = (answer: PerseusNumericInputAnswer) => { const stringAnswer = `${answer.value}`; + + // Always validate against the provided answer forms (pi, decimal, etc.) + const validatorForms = [...(answer.answerForms ?? [])]; + + // When an answer is set to strict, we validate using ONLY + // the provided answerForms. If strict is false, or if there + // were no provided answer forms, we will include all + // of the default answer forms in our validator. + if (!answer.strict || validatorForms.length === 0) { + validatorForms.push(...defaultAnswerForms); + } + return KhanAnswerTypes.number.createValidatorFunctional( stringAnswer, { @@ -169,19 +186,14 @@ export class NumericInput extends React.Component { : "optional", inexact: true, // TODO(merlob) backfill / delete maxError: answer.maxError, - forms: - answer.strict && - answer.answerForms && - answer.answerForms.length !== 0 - ? answer.answerForms - : allAnswerForms, + forms: validatorForms, }, ); }; // We may have received TeX; try to parse it before grading. // If `currentValue` is not TeX, this should be a no-op. - const currentValue = ParseTex(useInput.currentValue); + const currentValue = ParseTex(userInput.currentValue); const correctAnswers = rubric.answers.filter( (answer) => answer.status === "correct", ); From 332d5d6d053b84979c38f9781edd1474e514b21f Mon Sep 17 00:00:00 2001 From: Ned Redmond Date: Tue, 3 Oct 2023 17:26:03 -0400 Subject: [PATCH 06/11] Fix hints list item rendering (#672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LC-1022 Removes legacy style overrides from `ul` Accidentally fixes LC-1225 as well! Author: nedredmond Reviewers: jeremywiebe, nedredmond Required Reviewers: Approved By: jeremywiebe Checks: ✅ codecov/project, ✅ codecov/patch, ✅ Upload Coverage, ✅ Publish npm snapshot (ubuntu-latest, 16.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 16.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 16.x), ✅ Cypress (ubuntu-latest, 16.x), ✅ Extract i18n strings (ubuntu-latest, 16.x), ✅ Check builds for changes in size (ubuntu-latest, 16.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 16.x), ✅ gerald, ✅ Jest Coverage (ubuntu-latest, 16.x) Pull Request URL: https://github.com/Khan/perseus/pull/672 --- .changeset/tidy-radios-report.md | 5 +++++ .../__snapshots__/server-item-renderer.test.tsx.snap | 6 +++--- packages/perseus/src/hint-renderer.tsx | 1 + .../__snapshots__/multi-renderer.test.tsx.snap | 2 +- packages/perseus/src/styles/khan-exercise.css | 10 ++++++---- packages/perseus/src/styles/perseus-renderer.less | 1 - 6 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .changeset/tidy-radios-report.md diff --git a/.changeset/tidy-radios-report.md b/.changeset/tidy-radios-report.md new file mode 100644 index 0000000000..c308cae4fa --- /dev/null +++ b/.changeset/tidy-radios-report.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +Fix list item rendering in exercises and HintRenderer (LC-1022 & LC-1225) diff --git a/packages/perseus/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap b/packages/perseus/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap index 12d8c237a9..32394690a6 100644 --- a/packages/perseus/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +++ b/packages/perseus/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap @@ -189,7 +189,7 @@ exports[`server item renderer should snapshot: initial render 1`] = ` class="" >
{ const {isMobile} = apiOptions; const classNames = classnames( + "hint", !isMobile && "perseus-hint-renderer", isMobile && css(styles.newHint), isMobile && lastRendered && css(styles.lastRenderedNewHint), diff --git a/packages/perseus/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap b/packages/perseus/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap index bf6a6554f5..fbe93d0efe 100644 --- a/packages/perseus/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +++ b/packages/perseus/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap @@ -1128,7 +1128,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = ` class="" >
Date: Wed, 4 Oct 2023 10:24:15 -0500 Subject: [PATCH 07/11] Move StatefulKeypadContextProvider into Perseus (#747) * move StatefulKeypadContextProvider into Perseus * changeset * respond to Jeremy's feedback * Add keypadActive state to context (#748) * add keypadActive state to context * changeset * remove circular dep --- .changeset/quiet-tigers-shave.md | 6 ++ .changeset/tall-mangos-poke.md | 6 ++ .../components/__tests__/integration.test.tsx | 22 ++----- .../src/components/keypad-context.ts | 37 ----------- .../src/components/keypad-context.tsx | 61 +++++++++++++++++++ .../keypad-legacy/provided-keypad.tsx | 16 ++++- .../src/components/keypad-switch.tsx | 15 ++++- .../src/components/keypad/mobile-keypad.tsx | 22 +++---- .../src/full-mobile-input.stories.tsx | 43 +++++++------ packages/math-input/src/index.ts | 5 +- packages/math-input/src/types.ts | 16 +++++ .../src/__tests__/item-renderer.test.tsx | 42 +++++++------ .../test-keypad-context-wrapper.tsx | 28 +++------ .../__tests__/expression-mobile.test.tsx | 27 +++----- 14 files changed, 196 insertions(+), 150 deletions(-) create mode 100644 .changeset/quiet-tigers-shave.md create mode 100644 .changeset/tall-mangos-poke.md delete mode 100644 packages/math-input/src/components/keypad-context.ts create mode 100644 packages/math-input/src/components/keypad-context.tsx diff --git a/.changeset/quiet-tigers-shave.md b/.changeset/quiet-tigers-shave.md new file mode 100644 index 0000000000..3f847f019d --- /dev/null +++ b/.changeset/quiet-tigers-shave.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/math-input": major +"@khanacademy/perseus": patch +--- + +Move StatefulKeypadContextProvider into math-input diff --git a/.changeset/tall-mangos-poke.md b/.changeset/tall-mangos-poke.md new file mode 100644 index 0000000000..16e41736e0 --- /dev/null +++ b/.changeset/tall-mangos-poke.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/math-input": major +"@khanacademy/perseus": patch +--- + +Hoist keypad active state into keypad context diff --git a/packages/math-input/src/components/__tests__/integration.test.tsx b/packages/math-input/src/components/__tests__/integration.test.tsx index 13047d9e79..a5f7914201 100644 --- a/packages/math-input/src/components/__tests__/integration.test.tsx +++ b/packages/math-input/src/components/__tests__/integration.test.tsx @@ -12,10 +12,10 @@ import React, {useState} from "react"; import {KeypadType} from "../../enums"; import MathInput from "../input/math-input"; -import KeypadContext from "../keypad-context"; +import {KeypadContext, StatefulKeypadContextProvider} from "../keypad-context"; import KeypadSwitch from "../keypad-switch"; -import type {KeypadAPI, KeypadConfiguration} from "../../types"; +import type {KeypadConfiguration} from "../../types"; const MQ = MathQuill.getInterface(2); @@ -73,25 +73,11 @@ function KeypadWithContext() { } function ConnectedMathInput({keypadConfiguration = defaultConfiguration}) { - const [keypadElement, setKeypadElement] = useState(); - const [renderer, setRenderer] = useState(null); - const [scrollableElement, setScrollableElement] = - useState(); - return ( - + - + ); } diff --git a/packages/math-input/src/components/keypad-context.ts b/packages/math-input/src/components/keypad-context.ts deleted file mode 100644 index c2d7282cbd..0000000000 --- a/packages/math-input/src/components/keypad-context.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * KeypadContext provides a way to the Keypad and (Server)ItemRenderer to - * communicate. - * - * The KeypadContext.Provider wraps the ExerciseFooter while KeypadContext.Consumer - * wraps each (Server)ItemRenderer render site and the Keypad rendered in the - * ExerciseFooter. - */ -import * as React from "react"; - -import type {KeypadAPI} from "../types"; -import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core"; - -type KeypadContext = { - setKeypadElement: (keypadElement?: KeypadAPI) => void; - keypadElement: KeypadAPI | null | undefined; - setRenderer: ( - renderer?: KeypadContextRendererInterface | null | undefined, - ) => void; - renderer: KeypadContextRendererInterface | null | undefined; - setScrollableElement: ( - scrollableElement?: HTMLElement | null | undefined, - ) => void; - scrollableElement: HTMLElement | null | undefined; -}; - -// @ts-expect-error - TS2322 - Type 'Context<{ setKeypadElement: (keypadElement: HTMLElement | null | undefined) => void; keypadElement: null; setRenderer: (renderer: RendererInterface | null | undefined) => void; renderer: null; setScrollableElement: (scrollableElement: HTMLElement | ... 1 more ... | undefined) => void; scrollableElement: null; }>' is not assignable to type 'Context'. -const context: React.Context = React.createContext({ - setKeypadElement: (keypadElement) => {}, - keypadElement: null, - setRenderer: (renderer) => {}, - renderer: null, - setScrollableElement: (scrollableElement) => {}, - scrollableElement: null, -}); - -export default context; diff --git a/packages/math-input/src/components/keypad-context.tsx b/packages/math-input/src/components/keypad-context.tsx new file mode 100644 index 0000000000..28519d5a33 --- /dev/null +++ b/packages/math-input/src/components/keypad-context.tsx @@ -0,0 +1,61 @@ +/** + * KeypadContext provides a way to the Keypad and Perseus Renderers to + * communicate. + * + * The StatefulKeypadContextProvider wraps the application + * while KeypadContext.Consumer wraps things that need this state: + * - mobile keypad usages + * - Perseus Renderers (Server/Item/Article) + */ +import * as React from "react"; +import {useState} from "react"; + +import type {KeypadAPI, KeypadContextType} from "../types"; +import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core"; + +// @ts-expect-error - TS2322 - Type 'Context<{ setKeypadElement: (keypadElement: HTMLElement | null | undefined) => void; keypadElement: null; setRenderer: (renderer: RendererInterface | null | undefined) => void; renderer: null; setScrollableElement: (scrollableElement: HTMLElement | ... 1 more ... | undefined) => void; scrollableElement: null; }>' is not assignable to type 'Context'. +export const KeypadContext: React.Context = + React.createContext({ + setKeypadActive: (keypadActive) => {}, + keypadActive: false, + setKeypadElement: (keypadElement) => {}, + keypadElement: null, + setRenderer: (renderer) => {}, + renderer: null, + setScrollableElement: (scrollableElement) => {}, + scrollableElement: null, + }); + +type Props = React.PropsWithChildren; + +export function StatefulKeypadContextProvider(props: Props) { + // whether or not to display the keypad + const [keypadActive, setKeypadActive] = useState(false); + // used to communicate between the keypad and the Renderer + const [keypadElement, setKeypadElement] = useState(); + // this is a KeypadContextRendererInterface from Perseus + const [renderer, setRenderer] = + useState(); + const [scrollableElement, setScrollableElement] = + useState(); + + return ( + + {props.children} + + ); +} diff --git a/packages/math-input/src/components/keypad-legacy/provided-keypad.tsx b/packages/math-input/src/components/keypad-legacy/provided-keypad.tsx index 752d6d0ae5..94dfe821a4 100644 --- a/packages/math-input/src/components/keypad-legacy/provided-keypad.tsx +++ b/packages/math-input/src/components/keypad-legacy/provided-keypad.tsx @@ -22,6 +22,8 @@ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; import type {StyleType} from "@khanacademy/wonder-blocks-core"; type Props = { + setKeypadActive: (keypadActive: boolean) => void; + keypadActive: boolean; onElementMounted?: (arg1: any) => void; onDismiss?: () => void; style?: StyleType; @@ -37,12 +39,22 @@ class ProvidedKeypad extends React.Component implements KeypadAPI { this.store = createStore(); } + componentDidUpdate(prevProps) { + if (this.props.keypadActive && !prevProps.keypadActive) { + this.store.dispatch(activateKeypad()); + } + + if (!this.props.keypadActive && prevProps.keypadActive) { + this.store.dispatch(dismissKeypad()); + } + } + activate: () => void = () => { - this.store.dispatch(activateKeypad()); + this.props.setKeypadActive(true); }; dismiss: () => void = () => { - this.store.dispatch(dismissKeypad()); + this.props.setKeypadActive(false); }; configure: (configuration: KeypadConfiguration, cb: () => void) => void = ( diff --git a/packages/math-input/src/components/keypad-switch.tsx b/packages/math-input/src/components/keypad-switch.tsx index b38a92eeb7..a1ead10df1 100644 --- a/packages/math-input/src/components/keypad-switch.tsx +++ b/packages/math-input/src/components/keypad-switch.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import {MobileKeypad} from "./keypad"; +import {KeypadContext} from "./keypad-context"; import LegacyKeypad from "./keypad-legacy"; import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; @@ -23,7 +24,19 @@ function KeypadSwitch(props: Props) { // Note: Although we pass the "onAnalyticsEvent" to both keypad components, // only the current one uses it. There's no point in instrumenting the // legacy keypad given that it's on its way out the door. - return ; + return ( + + {({setKeypadActive, keypadActive}) => { + return ( + + ); + }} + + ); } export default KeypadSwitch; diff --git a/packages/math-input/src/components/keypad/mobile-keypad.tsx b/packages/math-input/src/components/keypad/mobile-keypad.tsx index dbf8b5b2c3..b9d39bb3f6 100644 --- a/packages/math-input/src/components/keypad/mobile-keypad.tsx +++ b/packages/math-input/src/components/keypad/mobile-keypad.tsx @@ -4,6 +4,7 @@ import ReactDOM from "react-dom"; import {View} from "../../fake-react-native-web/index"; +import Keypad from "./keypad"; import {expandedViewThreshold} from "./utils"; import type Key from "../../data/keys"; @@ -16,8 +17,6 @@ import type { import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; import type {StyleType} from "@khanacademy/wonder-blocks-core"; -import Keypad from "./index"; - /** * This is the v2 equivalent of v1's ProvidedKeypad. It follows the same * external API so that it can be hot-swapped with the v1 keypad and @@ -34,10 +33,11 @@ type Props = { onDismiss?: () => void; style?: StyleType; onAnalyticsEvent: AnalyticsEventHandlerFn; + setKeypadActive: (keypadActive: boolean) => void; + keypadActive: boolean; }; type State = { - active: boolean; containerWidth: number; hasBeenActivated: boolean; keypadConfig?: KeypadConfiguration; @@ -52,7 +52,6 @@ class MobileKeypad extends React.Component implements KeypadAPI { hasMounted = false; state: State = { - active: false, containerWidth: 0, hasBeenActivated: false, }; @@ -109,16 +108,15 @@ class MobileKeypad extends React.Component implements KeypadAPI { }; activate: () => void = () => { + this.props.setKeypadActive(true); this.setState({ - active: true, hasBeenActivated: true, }); }; dismiss: () => void = () => { - this.setState({active: false}, () => { - this.props.onDismiss?.(); - }); + this.props.setKeypadActive(false); + this.props.onDismiss?.(); }; configure: (configuration: KeypadConfiguration, cb: () => void) => void = ( @@ -162,14 +160,14 @@ class MobileKeypad extends React.Component implements KeypadAPI { } render(): React.ReactNode { - const {style} = this.props; - const {active, hasBeenActivated, containerWidth, cursor, keypadConfig} = + const {keypadActive, style} = this.props; + const {hasBeenActivated, containerWidth, cursor, keypadConfig} = this.state; const containerStyle = [ // internal styles styles.keypadContainer, - active && styles.activeKeypadContainer, + keypadActive && styles.activeKeypadContainer, // styles passed as props ...(Array.isArray(style) ? style : [style]), ]; @@ -179,7 +177,7 @@ class MobileKeypad extends React.Component implements KeypadAPI { // during the initial render. // Done inline (dynamicStyle) since stylesheets might not be loaded yet. let dynamicStyle = {}; - if (!active && !hasBeenActivated) { + if (!keypadActive && !hasBeenActivated) { dynamicStyle = {visibility: "hidden"}; } diff --git a/packages/math-input/src/full-mobile-input.stories.tsx b/packages/math-input/src/full-mobile-input.stories.tsx index 9f1aff494c..ab843d08eb 100644 --- a/packages/math-input/src/full-mobile-input.stories.tsx +++ b/packages/math-input/src/full-mobile-input.stories.tsx @@ -1,9 +1,13 @@ import {action} from "@storybook/addon-actions"; import * as React from "react"; -import type {KeypadAPI} from "./types"; - -import {KeypadInput, KeypadType, MobileKeypad} from "./index"; +import { + KeypadInput, + KeypadType, + MobileKeypad, + StatefulKeypadContextProvider, + KeypadContext, +} from "./index"; export default { title: "math-input/Full Mobile MathInput", @@ -19,32 +23,19 @@ export default { }, }; -export const Basic = () => { +const Basic = ({keypadElement, setKeypadElement}) => { const [value, setValue] = React.useState(""); - // Reference to the keypad - const [keypadElement, setKeypadElement] = React.useState(); // Whether to use Expression or Fraction keypad const [expression, setExpression] = React.useState(false); // Whether to use CDOT or TIMES const [times, setTimes] = React.useState(true); // Whether to use v1 or v2 keypad const [v2Keypad, setV2Keypad] = React.useState(true); - // Whether the keypad is open or not - const [keypadOpen, setKeypadOpen] = React.useState(false); const input = React.useRef(null); const timesLabel = times ? "CDOT" : "TIMES"; - const toggleKeypad = () => { - if (keypadOpen) { - keypadElement?.dismiss(); - } else { - keypadElement?.activate(); - } - setKeypadOpen(!keypadOpen); - }; - React.useEffect(() => { keypadElement?.configure( { @@ -73,9 +64,6 @@ export const Basic = () => { - @@ -111,3 +99,18 @@ export const Basic = () => {
); }; + +export function Wrapped() { + return ( + + + {({keypadElement, setKeypadElement}) => ( + + )} + + + ); +} diff --git a/packages/math-input/src/index.ts b/packages/math-input/src/index.ts index 6b43edd96e..3a800680fa 100644 --- a/packages/math-input/src/index.ts +++ b/packages/math-input/src/index.ts @@ -30,7 +30,10 @@ export {default as MobileKeypad} from "./components/keypad-switch"; export {default as DesktopKeypad} from "./components/keypad"; // Context used to pass data/refs around -export {default as KeypadContext} from "./components/keypad-context"; +export { + KeypadContext, + StatefulKeypadContextProvider, +} from "./components/keypad-context"; // External API of the "Provided" keypad component export {keypadElementPropType} from "./components/prop-types"; diff --git a/packages/math-input/src/types.ts b/packages/math-input/src/types.ts index 0c051d2905..83c947b9e0 100644 --- a/packages/math-input/src/types.ts +++ b/packages/math-input/src/types.ts @@ -7,6 +7,7 @@ import type { KeyType, KeypadType, } from "./enums"; +import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core"; import type * as React from "react"; import type ReactDOM from "react-dom"; @@ -105,3 +106,18 @@ export interface KeypadAPI { setKeyHandler: (keyHandler: KeyHandler) => void; getDOMNode: () => ReturnType; } + +export type KeypadContextType = { + setKeypadActive: (keypadActive: boolean) => void; + keypadActive: boolean; + setKeypadElement: (keypadElement?: KeypadAPI) => void; + keypadElement: KeypadAPI | null | undefined; + setRenderer: ( + renderer?: KeypadContextRendererInterface | null | undefined, + ) => void; + renderer: KeypadContextRendererInterface | null | undefined; + setScrollableElement: ( + scrollableElement?: HTMLElement | null | undefined, + ) => void; + scrollableElement: HTMLElement | null | undefined; +}; diff --git a/packages/perseus/src/__tests__/item-renderer.test.tsx b/packages/perseus/src/__tests__/item-renderer.test.tsx index 4b9fd676b8..67d760d8df 100644 --- a/packages/perseus/src/__tests__/item-renderer.test.tsx +++ b/packages/perseus/src/__tests__/item-renderer.test.tsx @@ -1,5 +1,6 @@ +import {StatefulKeypadContextProvider} from "@khanacademy/math-input"; import {RenderStateRoot} from "@khanacademy/wonder-blocks-core"; -import {fireEvent, render, screen} from "@testing-library/react"; +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import * as React from "react"; import "@testing-library/jest-dom"; // Imports custom matchers @@ -71,23 +72,25 @@ export const renderQuestion = ( let renderer: ItemRenderer | null = null; const {container} = render( - (renderer = node)} - apiOptions={apiOptions} - item={question} - problemNum={0} - reviewMode={false} - savedState="" - controlPeripherals={false} - dependencies={testDependenciesV2} - {...optionalProps} - /> - {/* The ItemRenderer _requires_ two divs: a work area and hints + + (renderer = node)} + apiOptions={apiOptions} + item={question} + problemNum={0} + reviewMode={false} + savedState="" + controlPeripherals={false} + dependencies={testDependenciesV2} + {...optionalProps} + /> + {/* The ItemRenderer _requires_ two divs: a work area and hints area. Without both of these, it fails to render anything! */} -
-
+
+
- + + , ); if (!renderer) { @@ -309,18 +312,21 @@ describe("item renderer", () => { ); }); - it("should activate the keypad when widget with input is focused", () => { + it("should activate the keypad when widget with input is focused", async () => { // Arrange const {renderer} = renderQuestion(itemWithInput, { isMobile: true, customKeypad: true, + useV2Keypad: true, }); // Act renderer.focus(); // Assert - expect(screen.getByLabelText("7")).toBeVisible(); + await waitFor(() => { + expect(screen.getByLabelText("7")).toBeVisible(); + }); }); it("should provide current and previous focus paths on focus change to, and away from, a single widget", () => { diff --git a/packages/perseus/src/widgets/__stories__/test-keypad-context-wrapper.tsx b/packages/perseus/src/widgets/__stories__/test-keypad-context-wrapper.tsx index e244af6a05..004e5f49fe 100644 --- a/packages/perseus/src/widgets/__stories__/test-keypad-context-wrapper.tsx +++ b/packages/perseus/src/widgets/__stories__/test-keypad-context-wrapper.tsx @@ -1,4 +1,8 @@ -import {KeypadContext, MobileKeypad} from "@khanacademy/math-input"; +import { + KeypadContext, + StatefulKeypadContextProvider, + MobileKeypad, +} from "@khanacademy/math-input"; import {View} from "@khanacademy/wonder-blocks-core"; import {action} from "@storybook/addon-actions"; import {StyleSheet} from "aphrodite"; @@ -29,29 +33,11 @@ type Props = { }; const TestKeypadContextWrapper = (props: Props): React.ReactElement => { - const [keypadElement, setKeypadElement] = React.useState(null); - const [renderer, setRenderer] = React.useState(null); - const [scrollableElement, setScrollableElement] = React.useState( - document.body, - ); - return ( - >' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'. - setKeypadElement, - keypadElement, - // @ts-expect-error - TS2322 - Type 'Dispatch>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'. - setRenderer, - renderer, - // @ts-expect-error - TS2322 - Type 'Dispatch>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'. - setScrollableElement, - scrollableElement, - }} - > + {props.children}