feat: align files with commit 5d52a28 and f9dff0 (#210)

This PR aims to fix the commit mistakes I made when trying to merge with a refactored fork. This will keep the changes I made in the refactor.
This commit is contained in:
Raymond Zhou
2023-01-24 06:43:01 -08:00
committed by GitHub
parent acee24eaa7
commit f135bd2b4a
153 changed files with 4757 additions and 4075 deletions

View File

@@ -36,4 +36,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
run: npx semantic-release
run: npx semantic-release@19.0.5

View File

@@ -10,7 +10,7 @@ This guide presumes you have a functioning devstack.
2. In devstack + venv, run `$ make dev.provision.lms+studio+frontend-app-course-authoring` to make up the required services. Minimum services required are lms, studio and frontend-app-course-authoring.
4. In [Studio Django Admin](http://localhost:18000/admin/waffle/flag/) turn on `new_core_editors.use_new_text_editor` flag for HTML editor, `new_core_editors.use_new_video_editor` flag for new video editor, and `new_core_editors.use_new_problem_editor` flag for problems. The list of supported flags is in [toggles.py. ](https://github.com/openedx/edx-platform/blob/master/cms/djangoapps/contentstore/toggles.py). you might have to add a flag for your xblock of choice.
2. Clone this repo into the [`${DEVSTACK_WORKSPACE}/src` directory](https://edx.readthedocs.io/projects/open-edx-devstack/en/latest/readme.html?highlight=DEVSTACK_WORKSPACE#id9) the sibling repo of your edx devstack `/src`.
3. In the course authoring app, follow the guide to use your [local verison of frontend-lib-content-components. ](https://github.com/openedx/frontend-build#local-module-configuration-for-webpack) The moduleconfig.js in the frontend-app-course-authoring repo will be:
3. In the course authoring app, follow the guide to use your [local verison of frontend-lib-content-components. ](https://github.com/openedx/frontend-build#local-module-configuration-for-webpack) The module.config.js in the frontend-app-course-authoring repo will be:
```
module.exports = {

View File

@@ -0,0 +1,136 @@
# Internal editor testability decision
# Increased complexity for the sake of testability
The internally-managed editors in this repo (as of now planned to include text, video, and problem types) follow a number of patterns that increase the complexity of parts of the code slightly, in favor of providing increased testability around their behavior.
## Note - Strict Dictionaries
Javacript is generally fairly lackadaisical with regards to dictionary access of missing/invalid keys. This is fine and expected in many cases, but also prevents us from using dictionary access on something like a key store to ensure we are calling something that actually exists.
For this purpose, there are a pair of utilities in this repo called `StrictDict` and `keyStore`.
`StrictDict` takes an object and returns a version that will complain (throw an error) if called with an invalid key.
`keyStore` takes an object and returns a StrictDict of just the keys of that object. (this is useful particularly for mocking and spying on specific methods and fields)
## Note - Self imports
Javascript webpack imports can be problematic around the specific issue of attempting to mock a single method being used in another method in the same file.
Problem: File A includes methodA and methodB (which calls methodA). We want to be able to test methodA and then test methodB *without* having to re-test methodA as part of that test. We want to be able to mock methodA *only* while we are testing methodB.
Solution: Self-imports. By importing the module into itself (which webpack handles nicely, don't worry), we provide tests the ability to spy on and mock individual methods from that module separately on a per-test basis.
Ex:
```javascript
// myFile.js
import * as module from './myFile';
export const methodA = (val) => // do something complex with val and return a number
export const methodB = (val) => module.methodA(val) * 2;
// myFile.test.js
import * as module from './myFile';
import { keyStore } from './utils';
cosnt moduleKeys = keyStore(module);
describe('myFile', () => {
describe('methodA', () => ...);
describe('methodB', () => {
const mockMethodA = (val) => val + 3
const testValue = 23;
beforeEach(() => {
jest.spyOn(module, moduleKeys).mockImplementationValueOnce(mockMethodA);
});
it('returns double the output of methodA with the given value', () => {
expect(module.methodB(testValue)).toEqual(mockMethodA(testValue) + 3);
});
});
});
```
## Hooks and Snapshots - Separation from components for increased viability of snapshots
As part of the testing of these internal editors, we are relying on snapshot testing to ensure stability of the display of the components themselves. This can be a fragile solution in certain situations where components are too large or complex to adequately snapshot and verify.
For this purpose, we have opted for a general pattern of separating all of the behavior of components withing these editors into separate `hooks` files.
These hook files contain methods that utilize both `react` and `react-redux` hooks, along with arguments passed directly into the component, in order to generate the resulting desired behaviors.
From there, components are tested by mocking out the behavior of these hooks to return verifyable data in the snapshots.
As part of this separation, there are a number of additional patterns that are followed
### Snapshot considerations
#### Callbacks
Any callback that is included in render in a component should be separated such that is is either passed in as a prop or derived from a hook, and should be mocked with a `mockName` using jest, to ensure that they are uniquely identifyable in the snapshots.
Ex:
```javascript
const props = {
onClick: jest.fn().mockName('props.onClick');
}
expect(shallow(<MyElement {...props} />)).toMatchSnapshot();
```
#### Imported components
Imported compoents are mocked to return simple string components based on their existing name. This results in shallow renders that display the components by name, with passed props, but do not attempt to render *any* logic from those components.
This is a bit more complex for components with sub-components, but we have provided a test utility in `src/testUtils` called `mockNestedComponent` that will allow you to easily mock these for your snapshots as well.
Ex:
```javascript
jest.mock('componentModule', () => {
const { mockNestedComponent } = jest.requireActual('./testUtils');
return {
SimpleComponents: () => 'SimpleComponent',
NestedComponent: mockNestedComponent('NestedComponent', {
NestedChild1: 'NestedComponent.NestedChild1',
NestedChild2: 'NestedComponent.NestedChild2',
}),
});
```
#### Top-level mocked imports
We have mocked out all paragon components and icons being used in the repo, as well as a number of other common methods, hooks, and components in our module's `setupTests` file, which will ensure that those components show up reasonably in snapshots.
### Hook Considerations
#### useState and mockUseState
React's useState hook is a very powerful alternative to class components, but is also somewhat problematic to test, as it returns different values based on where it is called in a hook, as well as based on previous runs of that hook.
To resolve this, we are using a custom test utility to mock a hook modules state values for easy testing.
This requires a particular structure to hook modules that use the useState, for the sake of enabling the mock process (which is documented with the utility).
Ex:
```javascript
import * as module from './hooks';
const state = {
myStateValue: (val) => useState(val),
};
const myHook = () => {
const [myStateValue, setMyStateValue] = module.state.myStateValue('initialValue');
};
```
Examples on how to use this for testing are included with the mock class in `src/testUtils`
#### useCallback, useEffect
These hooks provide behavior that calls a method based on given prerequisite behaviors.
For this reason, we use general-purpose mocks for these hooks that return an object containing the passed callback and prerequisites for easy test access.
#### Additional Considrations
*useIntl not available*
We are using react-intl under the hood for our i18n support, but do not have access to some of the more recent features in that library due to the pinned version in frontend-platform. Specifically, this includes a `useIntl` hook available in later versions that is still unavailable to us, requiring us to use the older `injectIntl` pattern.
*useDispatch*
React-redux's `useDispatch` hook does not play nicely with being called in a method called by a component, and really wants to be called *in* the component. For this reason, the dispatch method is generated in components and passed through to hook components.
## Notes for integration testing
Because of the top-level mocks in setupTest, any integration tests will need to be sure to unmock most of these.
Ex:
```javascript
jest.unmock('@edx/frontend-platform/i18n');
jest.unmock('@edx/paragon');
jest.unmock('@edx/paragon/icons');
jest.unmock('react-redux');
```

1836
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "AGPL-3.0",
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-xml": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@reduxjs/toolkit": "^1.8.1",
@@ -35,9 +36,9 @@
"devDependencies": {
"@edx/frontend-build": "^11.0.2",
"@edx/frontend-platform": "2.4.0",
"@edx/paragon": "^20.21.3",
"@edx/paragon": "^20.27.0",
"@testing-library/dom": "^8.13.0",
"@testing-library/react": "12.1.1",
"@testing-library/react": "^12.1.1",
"@testing-library/user-event": "^13.5.0",
"codecov": "3.8.3",
"enzyme": "3.11.0",
@@ -192,15 +193,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/code-frame/node_modules/@babel/highlight": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
@@ -437,24 +429,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -478,18 +452,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/core/node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -525,20 +487,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -638,6 +586,36 @@
"node": ">=0.10.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.0.tgz",
@@ -796,36 +774,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -861,20 +809,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/plugin-proposal-class-properties/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -1444,24 +1378,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -1486,18 +1402,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz",
@@ -2466,20 +2370,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-env/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -2722,24 +2612,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-react/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-react/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/preset-react/node_modules/@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -2829,20 +2701,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-react/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
@@ -2854,6 +2712,20 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
@@ -2912,14 +2784,6 @@
"@lezer/javascript": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/javascript": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.0.tgz",
@@ -2929,12 +2793,16 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"node_modules/@codemirror/lang-xml": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.1.tgz",
"integrity": "sha512-0tvycUTElajCcRKgsszhKjWX+uuOogdu5+enpfqYA+j0gnP8ek7LRxujh2/XMPRdXt/hwOML4slJLE7r2eX3yQ==",
"dependencies": {
"@lezer/common": "^1.0.0"
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/xml": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
@@ -2950,22 +2818,6 @@
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/language/node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/language/node_modules/@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/language/node_modules/style-mod": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
@@ -3288,12 +3140,6 @@
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
"dev": true
},
"node_modules/@edx/frontend-build/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@edx/frontend-build/node_modules/@types/webpack": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz",
@@ -4481,9 +4327,9 @@
}
},
"node_modules/@edx/paragon": {
"version": "20.21.5",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.21.5.tgz",
"integrity": "sha512-7+pRDS3MejiF3tsOFs9R6PC66zZJb8eN2nYObNWnyUmKZ7DfzzpHVsDuUYUg+J5cm3dYMY2imyIrMrqov6ettA==",
"version": "20.27.0",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.27.0.tgz",
"integrity": "sha512-jy62ZEBdAVlsP6tAm1/YDyMtc9fiD47H00whoW+y2Z+lLZqPsv6D5boIPQIcdBeg0W4f2gCU4TEy2+b2q8mYGA==",
"dev": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
@@ -4567,23 +4413,6 @@
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==",
"dev": true
},
"node_modules/@edx/paragon/node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
},
"node_modules/@edx/paragon/node_modules/@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@edx/paragon/node_modules/@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -5242,7 +5071,7 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/css/node_modules/@lezer/highlight": {
"node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
@@ -5250,14 +5079,6 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/css/node_modules/@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/html": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.0.tgz",
@@ -5268,20 +5089,21 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/html/node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"node_modules/@lezer/lr": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.0.tgz",
"integrity": "sha512-rpvS+WPS/PlbJCiW+bzXPbIFIRXmzRiTEDzMvrvgpED05w5ZQO59AzH3BJen2AnHuJIlP3DcJRjsKLTrkknUNA==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/html/node_modules/@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"node_modules/@lezer/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-jMDXrV953sDAUEMI25VNrI9dz94Ai96FfeglytFINhhwQ867HKlCE2jt3AwZTCT7M528WxdDWv/Ty8e9wizwmQ==",
"dependencies": {
"@lezer/common": "^1.0.0"
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@mapbox/node-pre-gyp": {
@@ -5635,10 +5457,36 @@
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
"dev": true
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz",
"integrity": "sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
"dev": true
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz",
"integrity": "sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/webpack": {
"version": "6.2.1",
@@ -5931,24 +5779,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -5972,18 +5802,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/plugin-syntax-typescript": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz",
@@ -6083,20 +5901,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@svgr/webpack/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -6585,15 +6389,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/@svgr/webpack/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@svgr/webpack/node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
@@ -6702,16 +6497,42 @@
"dev": true
},
"node_modules/@types/babel__generator": {
"dev": true
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz",
"integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==",
"dev": true,
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"dev": true
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz",
"integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"dev": true
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.3.0"
}
},
"node_modules/@types/body-parser": {
"dev": true
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/cheerio": {
"version": "0.22.31",
@@ -6722,41 +6543,151 @@
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/eslint": {
"version": "8.4.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
"integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==",
"dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
"integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
"dev": true,
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
"dev": true
},
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
"dev": true
},
"node_modules/@types/prop-types": {
"dev": true
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"node_modules/@types/react": {},
"node_modules/@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/scheduler": {
"dev": true
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/@types/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==",
"dev": true,
"dependencies": {
"@types/mime": "*",
"@types/node": "*"
}
},
"node_modules/@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"node_modules/@types/uglify-js": {
"dev": true
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz",
"integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==",
"dev": true,
"dependencies": {
"source-map": "^0.6.1"
}
},
"node_modules/@types/webpack-sources": {
"dev": true
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz",
"integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/source-list-map": "*",
"source-map": "^0.7.3"
}
},
"node_modules/@types/webpack-sources/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.0",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"dev": true
},
"node_modules/@videojs/http-streaming": {
@@ -8333,36 +8264,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-eslint/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-eslint/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-eslint/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/babel-eslint/node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -8398,20 +8299,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-eslint/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-eslint/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -8512,50 +8399,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-jest/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-jest/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-jest/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/babel-jest/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-jest/node_modules/@cnakazawa/watch": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz",
@@ -8652,12 +8495,6 @@
"@types/node": "*"
}
},
"node_modules/babel-jest/node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"node_modules/babel-jest/node_modules/@types/istanbul-reports": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
@@ -9580,15 +9417,6 @@
"node": ">=0.10.0"
}
},
"node_modules/babel-jest/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/babel-jest/node_modules/source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -9705,12 +9533,6 @@
"webpack": ">=2"
}
},
"node_modules/babel-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/babel-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -9903,50 +9725,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@formatjs/ecma402-abstract": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.4.0.tgz",
@@ -10027,12 +9805,6 @@
"@types/node": "*"
}
},
"node_modules/babel-plugin-react-intl/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/babel-plugin-react-intl/node_modules/@types/schema-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/schema-utils/-/schema-utils-2.4.0.tgz",
@@ -10111,38 +9883,6 @@
"is-valid-path": "^0.1.1"
}
},
"node_modules/babel-plugin-transform-imports/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-transform-imports/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-plugin-transform-imports/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-polyfill": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
@@ -10204,36 +9944,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-preset-jest/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-preset-jest/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-preset-jest/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/babel-preset-jest/node_modules/@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
@@ -10395,20 +10105,6 @@
"node": ">=6.9.0"
}
},
"node_modules/babel-preset-jest/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/babel-preset-jest/node_modules/@types/babel__core": {
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz",
@@ -10422,15 +10118,6 @@
"@types/babel__traverse": "*"
}
},
"node_modules/babel-preset-jest/node_modules/@types/babel__traverse": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.3.0"
}
},
"node_modules/babel-preset-jest/node_modules/babel-plugin-jest-hoist": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz",
@@ -11247,15 +10934,6 @@
"source-map": "^0.6.0"
}
},
"node_modules/clean-webpack-plugin/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -11594,12 +11272,6 @@
"webpack": "^4.27.0 || ^5.0.0"
}
},
"node_modules/css-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/css-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -12496,15 +12168,6 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/cssnano/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/cssnano/node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -14755,12 +14418,6 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/file-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/file-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -16695,15 +16352,6 @@
"strip-ansi": "^6.0.1"
}
},
"node_modules/html-webpack-plugin/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/html-webpack-plugin/node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -16946,12 +16594,6 @@
"@types/node": "*"
}
},
"node_modules/image-webpack-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/image-webpack-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -20688,16 +20330,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/imagemin-svgo/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/imagemin-svgo/node_modules/svgo": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
@@ -22789,11 +22421,6 @@
}
}
},
"node_modules/jest-resolve": {
"dev": true,
"optional": true,
"peer": true
},
"node_modules/jest/node_modules/@babel/generator": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
@@ -22863,36 +22490,6 @@
"node": ">=6.9.0"
}
},
"node_modules/jest/node_modules/@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/jest/node_modules/@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/jest/node_modules/@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/jest/node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -22928,20 +22525,6 @@
"node": ">=6.9.0"
}
},
"node_modules/jest/node_modules/@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/jest/node_modules/@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -23291,15 +22874,6 @@
"node": ">= 6"
}
},
"node_modules/jest/node_modules/@types/babel__traverse": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.3.0"
}
},
"node_modules/jest/node_modules/@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
@@ -23309,12 +22883,6 @@
"@types/node": "*"
}
},
"node_modules/jest/node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"node_modules/jest/node_modules/@types/istanbul-reports": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
@@ -25560,15 +25128,6 @@
"node": ">=0.10.0"
}
},
"node_modules/jest/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jest/node_modules/source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -26501,12 +26060,6 @@
"webpack": "^4.4.0 || ^5.0.0"
}
},
"node_modules/mini-css-extract-plugin/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -26566,15 +26119,6 @@
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
"dev": true
},
"node_modules/mini-css-extract-plugin/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/mini-css-extract-plugin/node_modules/webpack-sources": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
@@ -28742,12 +28286,6 @@
"node": ">= 8"
}
},
"node_modules/react-dev-utils/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/react-dev-utils/node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -29440,17 +28978,6 @@
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/react-intl/node_modules/@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/react-intl/node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -30009,15 +29536,6 @@
"integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==",
"dev": true
},
"node_modules/resolve-url-loader/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve/node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -30641,6 +30159,15 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-loader": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz",
@@ -31292,12 +30819,6 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/style-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/style-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -32038,12 +31559,6 @@
}
}
},
"node_modules/url-loader/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/url-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -32814,12 +32329,6 @@
"@types/node": "*"
}
},
"node_modules/webpack-dev-server/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/webpack-dev-server/node_modules/@types/serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz",
@@ -34387,13 +33896,6 @@
"dev": true,
"peer": true
},
"node_modules/webpack/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true,
"peer": true
},
"node_modules/webpack/node_modules/@webassemblyjs/ast": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -34694,16 +34196,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/webpack/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/webpack/node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -35144,12 +34636,6 @@
"@babel/highlight": "^7.18.6"
},
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/highlight": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
@@ -35333,18 +34819,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -35362,12 +34836,6 @@
"@babel/types": "^7.20.5"
}
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -35397,17 +34865,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -35479,6 +34936,24 @@
}
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
"dev": true
},
"@babel/plugin-proposal-class-properties": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.0.tgz",
@@ -35595,24 +35070,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -35642,17 +35099,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -36090,18 +35536,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -36120,12 +35554,6 @@
"@babel/types": "^7.20.5"
}
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz",
@@ -36755,17 +36183,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -36951,18 +36368,6 @@
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
"dev": true
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -37018,17 +36423,6 @@
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
}
}
},
@@ -37040,6 +36434,17 @@
"regenerator-runtime": "^0.13.11"
}
},
"@babel/types": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@codemirror/autocomplete": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
@@ -37092,14 +36497,6 @@
"@lezer/javascript": "^1.0.0"
},
"dependencies": {
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/javascript": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.0.tgz",
@@ -37108,17 +36505,21 @@
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"requires": {
"@lezer/common": "^1.0.0"
}
}
}
},
"@codemirror/lang-xml": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.1.tgz",
"integrity": "sha512-0tvycUTElajCcRKgsszhKjWX+uuOogdu5+enpfqYA+j0gnP8ek7LRxujh2/XMPRdXt/hwOML4slJLE7r2eX3yQ==",
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/xml": "^1.0.0"
}
},
"@codemirror/language": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.2.tgz",
@@ -37132,22 +36533,6 @@
"style-mod": "^4.0.0"
},
"dependencies": {
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"style-mod": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
@@ -37393,12 +36778,6 @@
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/webpack": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz",
@@ -38316,9 +37695,9 @@
}
},
"@edx/paragon": {
"version": "20.21.5",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.21.5.tgz",
"integrity": "sha512-7+pRDS3MejiF3tsOFs9R6PC66zZJb8eN2nYObNWnyUmKZ7DfzzpHVsDuUYUg+J5cm3dYMY2imyIrMrqov6ettA==",
"version": "20.27.0",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-20.27.0.tgz",
"integrity": "sha512-jy62ZEBdAVlsP6tAm1/YDyMtc9fiD47H00whoW+y2Z+lLZqPsv6D5boIPQIcdBeg0W4f2gCU4TEy2+b2q8mYGA==",
"dev": true,
"requires": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
@@ -38384,23 +37763,6 @@
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
},
"@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"@types/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -38875,24 +38237,14 @@
"requires": {
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
},
"dependencies": {
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"requires": {
"@lezer/common": "^1.0.0"
}
}
}
},
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/html": {
@@ -38903,24 +38255,23 @@
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
},
"dependencies": {
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"requires": {
"@lezer/common": "^1.0.0"
}
}
}
},
"@lezer/lr": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.0.tgz",
"integrity": "sha512-rpvS+WPS/PlbJCiW+bzXPbIFIRXmzRiTEDzMvrvgpED05w5ZQO59AzH3BJen2AnHuJIlP3DcJRjsKLTrkknUNA==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-jMDXrV953sDAUEMI25VNrI9dz94Ai96FfeglytFINhhwQ867HKlCE2jt3AwZTCT7M528WxdDWv/Ty8e9wizwmQ==",
"requires": {
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"@mapbox/node-pre-gyp": {
@@ -39195,10 +38546,18 @@
}
},
"@svgr/babel-plugin-remove-jsx-attribute": {
"dev": true
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz",
"integrity": "sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==",
"dev": true,
"requires": {}
},
"@svgr/babel-plugin-remove-jsx-empty-expression": {
"dev": true
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz",
"integrity": "sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==",
"dev": true,
"requires": {}
},
"@svgr/webpack": {
"version": "6.2.1",
@@ -39419,18 +38778,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/helper-validator-option": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
@@ -39448,12 +38795,6 @@
"@babel/types": "^7.20.5"
}
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/plugin-syntax-typescript": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz",
@@ -39523,17 +38864,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -39842,12 +39172,6 @@
"boolbase": "^1.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
@@ -39928,16 +39252,42 @@
"dev": true
},
"@types/babel__generator": {
"dev": true
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz",
"integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@types/babel__template": {
"dev": true
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz",
"integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==",
"dev": true,
"requires": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"@types/babel__traverse": {
"dev": true
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"requires": {
"@babel/types": "^7.3.0"
}
},
"@types/body-parser": {
"dev": true
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/cheerio": {
"version": "0.22.31",
@@ -39948,41 +39298,150 @@
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/eslint": {
"version": "8.4.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
"integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==",
"dev": true,
"requires": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"@types/istanbul-lib-report": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
"integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
"dev": true
},
"@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true
},
"@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
"dev": true
},
"@types/prop-types": {
"dev": true
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"@types/react": {},
"@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"@types/scheduler": {
"dev": true
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==",
"dev": true,
"requires": {
"@types/mime": "*",
"@types/node": "*"
}
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/uglify-js": {
"dev": true
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz",
"integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
}
},
"@types/webpack-sources": {
"dev": true
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz",
"integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/source-list-map": "*",
"source-map": "^0.7.3"
},
"dependencies": {
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
}
}
},
"@types/yargs-parser": {
"version": "21.0.0",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
"dev": true
},
"@videojs/http-streaming": {
@@ -41126,24 +40585,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -41173,17 +40614,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
@@ -41259,35 +40689,6 @@
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
"dev": true
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@cnakazawa/watch": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz",
@@ -41369,12 +40770,6 @@
"@types/node": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"@types/istanbul-reports": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
@@ -42125,12 +41520,6 @@
}
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -42227,12 +41616,6 @@
"schema-utils": "^2.6.5"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -42375,35 +41758,6 @@
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
"dev": true
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@formatjs/ecma402-abstract": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.4.0.tgz",
@@ -42477,12 +41831,6 @@
"@types/node": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/schema-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/schema-utils/-/schema-utils-2.4.0.tgz",
@@ -42547,31 +41895,6 @@
"requires": {
"@babel/types": "^7.4",
"is-valid-path": "^0.1.1"
},
"dependencies": {
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
}
}
},
"babel-polyfill": {
@@ -42628,24 +41951,6 @@
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
"dev": true
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
@@ -42765,17 +42070,6 @@
"@babel/types": "^7.18.10"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@types/babel__core": {
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz",
@@ -42789,15 +42083,6 @@
"@types/babel__traverse": "*"
}
},
"@types/babel__traverse": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"requires": {
"@babel/types": "^7.3.0"
}
},
"babel-plugin-jest-hoist": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz",
@@ -43418,12 +42703,6 @@
"anymatch": "^3.0.0",
"source-map": "^0.6.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
@@ -43706,12 +42985,6 @@
"semver": "^7.3.5"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -44302,12 +43575,6 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -46041,12 +45308,6 @@
"schema-utils": "^3.0.0"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -47528,12 +46789,6 @@
"strip-ansi": "^6.0.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -47726,12 +46981,6 @@
"@types/node": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -50736,13 +49985,6 @@
"boolbase": "^1.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true
},
"svgo": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
@@ -52423,24 +51665,6 @@
"@babel/types": "^7.18.6"
}
},
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -52470,17 +51694,6 @@
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -52773,15 +51986,6 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"dev": true
},
"@types/babel__traverse": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz",
"integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==",
"dev": true,
"requires": {
"@babel/types": "^7.3.0"
}
},
"@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
@@ -52791,12 +51995,6 @@
"@types/node": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"@types/istanbul-reports": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
@@ -54588,12 +53786,6 @@
}
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -54931,11 +54123,6 @@
"dev": true,
"requires": {}
},
"jest-resolve": {
"dev": true,
"optional": true,
"peer": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -55358,12 +54545,6 @@
"webpack-sources": "^1.1.0"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -55405,12 +54586,6 @@
"integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"webpack-sources": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
@@ -57027,12 +56202,6 @@
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -57555,17 +56724,6 @@
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/react": {
"version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -58037,12 +57195,6 @@
"resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz",
"integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
@@ -58488,6 +57640,12 @@
"sort-keys": "^1.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-loader": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz",
@@ -58974,12 +58132,6 @@
"schema-utils": "^3.0.0"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -59562,12 +58714,6 @@
"schema-utils": "^3.0.0"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -59862,13 +59008,6 @@
"dev": true,
"peer": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true,
"peer": true
},
"@webassemblyjs/ast": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -60137,13 +59276,6 @@
"ajv-keywords": "^3.5.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"peer": true
},
"source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -60566,12 +59698,6 @@
"@types/node": "*"
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz",

View File

@@ -36,9 +36,9 @@
"devDependencies": {
"@edx/frontend-build": "^11.0.2",
"@edx/frontend-platform": "2.4.0",
"@edx/paragon": "^20.21.3",
"@edx/paragon": "^20.27.0",
"@testing-library/dom": "^8.13.0",
"@testing-library/react": "12.1.1",
"@testing-library/react": "^12.1.1",
"@testing-library/user-event": "^13.5.0",
"codecov": "3.8.3",
"enzyme": "3.11.0",
@@ -58,6 +58,7 @@
},
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-xml": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@reduxjs/toolkit": "^1.8.1",

View File

@@ -30,7 +30,7 @@ export const Editor = ({
const EditorComponent = supportedEditors[blockType];
return (
<div className="d-flex flex-column vh-100">
<div className="d-flex flex-column">
<div
className="pgn__modal-fullscreen"
role="dialog"

View File

@@ -2,7 +2,7 @@
exports[`Editor render presents error message if no relevant editor found and ref ready 1`] = `
<div
className="d-flex flex-column vh-100"
className="d-flex flex-column"
>
<div
aria-label="fAkEBlock"
@@ -20,7 +20,7 @@ exports[`Editor render presents error message if no relevant editor found and re
exports[`Editor render snapshot: renders correct editor given blockType (html -> TextEditor) 1`] = `
<div
className="d-flex flex-column vh-100"
className="d-flex flex-column"
>
<div
aria-label="html"

View File

@@ -1,7 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditorContainer component render snapshot: initialized. enable save and pass to header 1`] = `
<div>
<div
className="position-relative zindex-0"
>
<BaseModal
close={[MockFunction closeCancelConfirmModal]}
confirmAction={
<Button
onClick={
Object {
"handleCancel": Object {
"onClose": [MockFunction props.onClose],
},
}
}
variant="primary"
>
<FormattedMessage
defaultMessage="OK"
description="Label for OK button"
id="authoring.editorContainer.okButton.label"
/>
</Button>
}
footerAction={null}
isOpen={false}
size="md"
title="Exit the editor?"
>
<FormattedMessage
defaultMessage="Are you sure you want to exit the editor? Any unsaved changes will be lost."
description="Description text for modal confirming cancellation"
id="authoring.editorContainer.cancelConfirm.description"
/>
</BaseModal>
<ModalDialog.Header
className="shadow-sm zindex-10"
>
@@ -23,30 +56,22 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
>
<IconButton
iconAs="Icon"
onClick={
Object {
"handleCancelClicked": Object {
"onClose": [MockFunction props.onClose],
},
}
}
onClick={[MockFunction openCancelConfirmModal]}
src={[MockFunction icons.Close]}
/>
</div>
</ModalDialog.Title>
</ModalDialog.Header>
<h1>
My test content
</h1>
<ModalDialog.Body
className="pb-6"
>
<h1>
My test content
</h1>
</ModalDialog.Body>
<injectIntl(ShimmedIntlComponent)
disableSave={false}
onCancel={
Object {
"handleCancelClicked": Object {
"onClose": [MockFunction props.onClose],
},
}
}
onCancel={[MockFunction openCancelConfirmModal]}
onSave={
Object {
"handleSaveClicked": Object {
@@ -61,7 +86,40 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
`;
exports[`EditorContainer component render snapshot: not initialized. disable save and pass to header 1`] = `
<div>
<div
className="position-relative zindex-0"
>
<BaseModal
close={[MockFunction closeCancelConfirmModal]}
confirmAction={
<Button
onClick={
Object {
"handleCancel": Object {
"onClose": [MockFunction props.onClose],
},
}
}
variant="primary"
>
<FormattedMessage
defaultMessage="OK"
description="Label for OK button"
id="authoring.editorContainer.okButton.label"
/>
</Button>
}
footerAction={null}
isOpen={false}
size="md"
title="Exit the editor?"
>
<FormattedMessage
defaultMessage="Are you sure you want to exit the editor? Any unsaved changes will be lost."
description="Description text for modal confirming cancellation"
id="authoring.editorContainer.cancelConfirm.description"
/>
</BaseModal>
<ModalDialog.Header
className="shadow-sm zindex-10"
>
@@ -83,27 +141,18 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
>
<IconButton
iconAs="Icon"
onClick={
Object {
"handleCancelClicked": Object {
"onClose": [MockFunction props.onClose],
},
}
}
onClick={[MockFunction openCancelConfirmModal]}
src={[MockFunction icons.Close]}
/>
</div>
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body
className="pb-6"
/>
<injectIntl(ShimmedIntlComponent)
disableSave={true}
onCancel={
Object {
"handleCancelClicked": Object {
"onClose": [MockFunction props.onClose],
},
}
}
onCancel={[MockFunction openCancelConfirmModal]}
onSave={
Object {
"handleSaveClicked": Object {

View File

@@ -2,13 +2,7 @@
exports[`EditorFooter render snapshot: default args (disableSave: false, saveFailed: false) 1`] = `
<div
className="editor-footer"
style={
Object {
"bottom": 0,
"position": "sticky",
}
}
className="editor-footer fixed-bottom"
>
<ModalDialog.Footer
className="shadow-sm"
@@ -44,13 +38,7 @@ exports[`EditorFooter render snapshot: default args (disableSave: false, saveFai
exports[`EditorFooter render snapshot: save disabled. Show button spinner 1`] = `
<div
className="editor-footer"
style={
Object {
"bottom": 0,
"position": "sticky",
}
}
className="editor-footer fixed-bottom"
>
<ModalDialog.Footer
className="shadow-sm"
@@ -85,13 +73,7 @@ exports[`EditorFooter render snapshot: save disabled. Show button spinner 1`] =
exports[`EditorFooter render snapshot: save failed. Show error message 1`] = `
<div
className="editor-footer"
style={
Object {
"bottom": 0,
"position": "sticky",
}
}
className="editor-footer fixed-bottom"
>
<Toast
onClose={[MockFunction hooks.nullMethod]}

View File

@@ -21,7 +21,7 @@ export const EditorFooter = ({
// injected
intl,
}) => (
<div className="editor-footer" style={{ position: 'sticky', bottom: 0 }}>
<div className="editor-footer fixed-bottom">
{saveFailed && (
<Toast show onClose={nullMethod}>
<FormattedMessage {...messages.contentSaveFailed} />

View File

@@ -1,9 +1,12 @@
import { useSelector } from 'react-redux';
import { useState } from 'react';
import { RequestKeys } from '../../data/constants/requests';
import { selectors } from '../../data/redux';
import * as appHooks from '../../hooks';
import * as module from './hooks';
import analyticsEvt from '../../data/constants/analyticsEvt';
import { StrictDict } from '../../utils';
export const {
navigateCallback,
@@ -11,6 +14,10 @@ export const {
saveBlock,
} = appHooks;
export const state = StrictDict({
isCancelConfirmModalOpen: (val) => useState(val),
});
export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
const destination = useSelector(selectors.app.returnUrl);
const analytics = useSelector(selectors.app.analytics);
@@ -23,7 +30,16 @@ export const handleSaveClicked = ({ dispatch, getContent, validateEntry }) => {
validateEntry,
});
};
export const handleCancelClicked = ({ onClose }) => {
export const cancelConfirmModalToggle = () => {
const [isCancelConfirmOpen, setIsOpen] = module.state.isCancelConfirmModalOpen(false);
return {
isCancelConfirmOpen,
openCancelConfirmModal: () => setIsOpen(true),
closeCancelConfirmModal: () => setIsOpen(false),
};
};
export const handleCancel = ({ onClose }) => {
if (onClose) {
return onClose;
}
@@ -34,6 +50,6 @@ export const handleCancelClicked = ({ onClose }) => {
});
};
export const isInitialized = () => useSelector(selectors.app.isInitialized);
export const saveFailed = () => useSelector((state) => (
selectors.requests.isFailed(state, { requestKey: RequestKeys.saveBlock })
export const saveFailed = () => useSelector((rootState) => (
selectors.requests.isFailed(rootState, { requestKey: RequestKeys.saveBlock })
));

View File

@@ -1,4 +1,5 @@
import * as reactRedux from 'react-redux';
import { MockUseState } from '../../../testUtils';
import { RequestKeys } from '../../data/constants/requests';
import { selectors } from '../../data/redux';
@@ -7,6 +8,8 @@ import * as appHooks from '../../hooks';
import * as hooks from './hooks';
import analyticsEvt from '../../data/constants/analyticsEvt';
const hookState = new MockUseState(hooks);
jest.mock('../../data/redux', () => ({
selectors: {
app: {
@@ -67,9 +70,46 @@ describe('EditorContainer hooks', () => {
});
});
});
describe('handleCancelClicked', () => {
describe('cancelConfirmModalToggle', () => {
const hookKey = hookState.keys.isCancelConfirmModalOpen;
beforeEach(() => {
jest.clearAllMocks();
});
describe('state hook', () => {
hookState.testGetter(hookKey);
});
describe('using state', () => {
beforeEach(() => {
hookState.mock();
});
afterEach(() => {
hookState.restore();
});
describe('cancelConfirmModalToggle', () => {
let hook;
beforeEach(() => {
hook = hooks.cancelConfirmModalToggle();
});
test('isCancelConfirmOpen: state value', () => {
expect(hook.isCancelConfirmOpen).toEqual(hookState.stateVals[hookKey]);
});
test('openCancelConfirmModal: calls setter with true', () => {
hook.openCancelConfirmModal();
expect(hookState.setState[hookKey]).toHaveBeenCalledWith(true);
});
test('closeCancelConfirmModal: calls setter with false', () => {
hook.closeCancelConfirmModal();
expect(hookState.setState[hookKey]).toHaveBeenCalledWith(false);
});
});
});
});
describe('handleCancel', () => {
it('calls navigateCallback to returnUrl if onClose is not passed', () => {
expect(hooks.handleCancelClicked({})).toEqual(
expect(hooks.handleCancel({})).toEqual(
appHooks.navigateCallback({
destination: reactRedux.useSelector(selectors.app.returnUrl),
analyticsEvent: analyticsEvt.editorCancelClick,
@@ -79,7 +119,7 @@ describe('EditorContainer hooks', () => {
});
it('calls onClose and not navigateCallback if onClose is passed', () => {
const onClose = () => 'my close value';
expect(hooks.handleCancelClicked({ onClose })).toEqual(onClose);
expect(hooks.handleCancel({ onClose })).toEqual(onClose);
expect(appHooks.navigateCallback).not.toHaveBeenCalled();
});
});

View File

@@ -2,24 +2,50 @@ import React from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import { Icon, ModalDialog, IconButton } from '@edx/paragon';
import {
Icon, ModalDialog, IconButton, Button,
} from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import EditorFooter from './components/EditorFooter';
import TitleHeader from './components/TitleHeader';
import * as hooks from './hooks';
import BaseModal from '../TextEditor/components/BaseModal';
import messages from './messages';
export const EditorContainer = ({
children,
getContent,
onClose,
validateEntry,
// injected
intl,
}) => {
const dispatch = useDispatch();
const isInitialized = hooks.isInitialized();
const handleCancelClicked = hooks.handleCancelClicked({ onClose });
const { isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal } = hooks.cancelConfirmModalToggle();
const handleCancel = hooks.handleCancel({ onClose });
return (
<div>
<div
className="position-relative zindex-0"
>
<BaseModal
size="md"
confirmAction={(
<Button
variant="primary"
onClick={handleCancel}
>
<FormattedMessage {...messages.okButtonLabel} />
</Button>
)}
isOpen={isCancelConfirmOpen}
close={closeCancelConfirmModal}
title={intl.formatMessage(messages.cancelConfirmTitle)}
>
<FormattedMessage {...messages.cancelConfirmDescription} />
</BaseModal>
<ModalDialog.Header className="shadow-sm zindex-10">
<ModalDialog.Title>
<div
@@ -31,14 +57,16 @@ export const EditorContainer = ({
<IconButton
src={Close}
iconAs={Icon}
onClick={handleCancelClicked}
onClick={openCancelConfirmModal}
/>
</div>
</ModalDialog.Title>
</ModalDialog.Header>
{isInitialized && children}
<ModalDialog.Body className="pb-6">
{isInitialized && children}
</ModalDialog.Body>
<EditorFooter
onCancel={handleCancelClicked}
onCancel={openCancelConfirmModal}
onSave={hooks.handleSaveClicked({ dispatch, getContent, validateEntry })}
disableSave={!isInitialized}
saveFailed={hooks.saveFailed()}
@@ -55,6 +83,8 @@ EditorContainer.propTypes = {
getContent: PropTypes.func.isRequired,
onClose: PropTypes.func,
validateEntry: PropTypes.func,
// injected
intl: intlShape.isRequired,
};
export default EditorContainer;
export default injectIntl(EditorContainer);

View File

@@ -1,21 +1,28 @@
import { IconButton } from '@edx/paragon';
import { shallow } from 'enzyme';
import { useDispatch } from 'react-redux';
import { EditorContainer } from '.';
import * as hooks from './hooks';
import { formatMessage } from '../../../testUtils';
const props = {
getContent: jest.fn().mockName('props.getContent'),
onClose: jest.fn().mockName('props.onClose'),
validateEntry: jest.fn().mockName('props.validateEntry'),
// inject
intl: { formatMessage },
};
jest.mock('./hooks', () => ({
isInitialized: jest.fn().mockReturnValue(true),
handleCancelClicked: (args) => ({ handleCancelClicked: args }),
handleCancel: (args) => ({ handleCancel: args }),
handleSaveClicked: (args) => ({ handleSaveClicked: args }),
saveFailed: jest.fn().mockName('hooks.saveFailed'),
cancelConfirmModalToggle: jest.fn(() => ({
isCancelConfirmOpen: false,
openCancelConfirmModal: jest.fn().mockName('openCancelConfirmModal'),
closeCancelConfirmModal: jest.fn().mockName('closeCancelConfirmModal'),
})),
}));
let el;
@@ -39,23 +46,13 @@ describe('EditorContainer component', () => {
el = shallow(<EditorContainer {...props}>{testContent}</EditorContainer>);
});
test('close behavior is linked to modal onClose', () => {
const expected = hooks.handleCancelClicked({ onClose: props.onClose });
expect(el.find(IconButton)
.props().onClick).toEqual(expected);
});
test('close behavior is linked to footer onCancel', () => {
const expected = hooks.handleCancelClicked({ onClose: props.onClose });
expect(el.children().at(2)
.props().onCancel).toEqual(expected);
});
test('save behavior is linked to footer onSave', () => {
const expected = hooks.handleSaveClicked({
dispatch: useDispatch(),
getContent: props.getContent,
validateEntry: props.validateEntry,
});
expect(el.children().at(2)
expect(el.children().at(3)
.props().onSave).toEqual(expected);
});
});

View File

@@ -0,0 +1,19 @@
export const messages = {
cancelConfirmTitle: {
id: 'authoring.editorContainer.cancelConfirm.title',
defaultMessage: 'Exit the editor?',
description: 'Label for modal confirming cancellation',
},
cancelConfirmDescription: {
id: 'authoring.editorContainer.cancelConfirm.description',
defaultMessage: 'Are you sure you want to exit the editor? Any unsaved changes will be lost.',
description: 'Description text for modal confirming cancellation',
},
okButtonLabel: {
id: 'authoring.editorContainer.okButton.label',
defaultMessage: 'OK',
description: 'Label for OK button',
},
};
export default messages;

View File

@@ -2,57 +2,21 @@ import React, { memo } from 'react';
import { connect, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import {
Col, Collapsible, Icon, IconButton, Form, Row,
Collapsible,
Icon,
IconButton,
Form,
} from '@edx/paragon';
import { AddComment, Delete } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FeedbackOutline, DeleteOutline } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { selectors } from '../../../../../data/redux';
import { answerOptionProps } from '../../../../../data/services/cms/types';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import Checker from './components/Checker';
import { FeedbackBox } from './components/Feedback';
import * as hooks from './hooks';
const Checker = ({
hasSingleAnswer, answer, setAnswer,
}) => {
let CheckerType = Form.Checkbox;
if (hasSingleAnswer) {
CheckerType = Form.Radio;
}
return (
<CheckerType
className="pl-4 mt-3"
value={answer.id}
onChange={(e) => setAnswer({ correct: e.target.checked })}
checked={answer.correct}
>
{answer.id}
</CheckerType>
);
};
const FeedbackControl = ({
feedback, onChange, labelMessage, labelMessageBoldUnderline, key, answer, intl,
}) => (
<Form.Group key={key}>
<Form.Label className="mb-3">
<FormattedMessage
{...labelMessage}
values={{
answerId: answer.id,
boldunderline: <b><u><FormattedMessage {...labelMessageBoldUnderline} /></u></b>,
}}
/>
</Form.Label>
<Form.Control
placeholder={intl.formatMessage(messages.feedbackPlaceholder)}
value={feedback}
onChange={onChange}
/>
</Form.Group>
);
export const AnswerOption = ({
answer,
hasSingleAnswer,
@@ -66,91 +30,55 @@ export const AnswerOption = ({
const setAnswer = hooks.setAnswer({ answer, hasSingleAnswer, dispatch });
const { isFeedbackVisible, toggleFeedback } = hooks.prepareFeedback(answer);
const displayFeedbackControl = (answerObject) => {
if (problemType !== ProblemTypeKeys.MULTISELECT) {
return FeedbackControl({
key: `feedback-${answerObject.id}`,
feedback: answerObject.feedback,
onChange: (e) => setAnswer({ feedback: e.target.value }),
labelMessage: messages.selectedFeedbackLabel,
labelMessageBoldUnderline: messages.selectedFeedbackLabelBoldUnderlineText,
answer: answerObject,
intl,
});
}
return [
FeedbackControl({
key: `selectedfeedback-${answerObject.id}`,
feedback: answerObject.selectedFeedback,
onChange: (e) => setAnswer({ selectedFeedback: e.target.value }),
labelMessage: messages.selectedFeedbackLabel,
labelMessageBoldUnderline: messages.selectedFeedbackLabelBoldUnderlineText,
answer: answerObject,
intl,
}),
FeedbackControl({
key: `unselectedfeedback-${answerObject.id}`,
feedback: answerObject.unselectedFeedback,
onChange: (e) => setAnswer({ unselectedFeedback: e.target.value }),
labelMessage: messages.unSelectedFeedbackLabel,
labelMessageBoldUnderline: messages.unSelectedFeedbackLabelBoldUnderlineText,
answer: answerObject,
intl,
}),
];
};
return (
<Collapsible.Advanced
open={isFeedbackVisible}
onToggle={toggleFeedback}
className="collapsible-card"
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
>
<Row className="my-2">
<Col xs={1}>
<Checker
hasSingleAnswer={hasSingleAnswer}
<div className="answer-option-flex-item-1 mr-1 d-flex">
<Checker
hasSingleAnswer={hasSingleAnswer}
answer={answer}
setAnswer={setAnswer}
/>
</div>
<div className="answer-option-flex-item-2 ml-1">
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={(e) => { setAnswer({ title: e.target.value }); }}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
/>
<Collapsible.Body>
<FeedbackBox
problemType={problemType}
answer={answer}
setAnswer={setAnswer}
intl={intl}
/>
</Col>
<Col xs={10}>
<Form.Control
as="textarea"
rows={1}
value={answer.title}
onChange={(e) => { setAnswer({ title: e.target.value }); }}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
/>
<Collapsible.Body>
<div className="bg-dark-100 p-4 mt-3">
{displayFeedbackControl(answer)}
</div>
</Collapsible.Body>
</Col>
<Col xs={1} className="d-inline-flex mt-1">
<Collapsible.Trigger>
<IconButton
src={AddComment}
iconAs={Icon}
alt={intl.formatMessage(messages.feedbackToggleIconAltText)}
variant="primary"
/>
</Collapsible.Trigger>
</Collapsible.Body>
</div>
<div className="answer-option-flex-item-3 d-flex flex-row flex-nowrap">
<Collapsible.Trigger>
<IconButton
src={Delete}
src={FeedbackOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.answerDeleteIconAltText)}
onClick={removeAnswer}
alt={intl.formatMessage(messages.feedbackToggleIconAltText)}
variant="primary"
/>
</Col>
</Row>
</Collapsible.Trigger>
<IconButton
src={DeleteOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.answerDeleteIconAltText)}
onClick={removeAnswer}
variant="primary"
/>
</div>
</Collapsible.Advanced>
);
};
@@ -164,22 +92,6 @@ AnswerOption.propTypes = {
problemType: PropTypes.string.isRequired,
};
FeedbackControl.propTypes = {
feedback: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
labelMessage: PropTypes.string.isRequired,
labelMessageBoldUnderline: PropTypes.string.isRequired,
key: PropTypes.string.isRequired,
answer: answerOptionProps.isRequired,
intl: intlShape.isRequired,
};
Checker.propTypes = {
hasSingleAnswer: PropTypes.bool.isRequired,
answer: answerOptionProps.isRequired,
setAnswer: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => ({
problemType: selectors.problem.problemType(state),
});

View File

@@ -1,14 +1,23 @@
import React from 'react';
import { shallow } from 'enzyme';
import { formatMessage } from '../../../../../../testUtils';
import { AnswerOption } from './AnswerOption';
import { selectors } from '../../../../../data/redux';
import { AnswerOption, mapStateToProps } from './AnswerOption';
jest.mock('../../../../../data/redux', () => ({
selectors: {
problem: {
problemType: jest.fn(state => ({ problemType: state })),
},
},
}));
describe('AnswerOption', () => {
const answerWithOnlyFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
feedback: 'some feedback',
selectedFeedback: 'some feedback',
};
const answerWithSelectedUnselectedFeedback = {
id: 'A',
@@ -23,15 +32,23 @@ describe('AnswerOption', () => {
answer: answerWithOnlyFeedback,
// inject
intl: { formatMessage },
deleteAnswer: jest.fn(),
updateAnswer: jest.fn(),
// redux
problemType: 'multiplechoiceresponse',
};
describe('render', () => {
test('snapshot: renders correct option with feedback', () => {
expect(shallow(<AnswerOption {...props} />)).toMatchSnapshot();
});
test('snapshot: renders correct option with selected unselected feedback', () => {
expect(shallow(<AnswerOption {...props} answer={answerWithSelectedUnselectedFeedback} />)).toMatchSnapshot();
expect(shallow(<AnswerOption {...props} problemType="choiceresponse" answer={answerWithSelectedUnselectedFeedback} />)).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('problemType from problem.problemType', () => {
expect(
mapStateToProps(testState).problemType,
).toEqual(selectors.problem.problemType(testState));
});
});
});

View File

@@ -6,7 +6,7 @@ import { Add } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { initializeAnswerContainer } from '../../../hooks';
import { useAnswerContainer, isSingleAnswerProblem } from './hooks';
import { actions, selectors } from '../../../../../data/redux';
import { answerOptionProps } from '../../../../../data/services/cms/types';
import AnswerOption from './AnswerOption';
@@ -16,11 +16,14 @@ export const AnswersContainer = ({
// Redux
answers,
addAnswer,
updateField,
}) => {
const { hasSingleAnswer } = initializeAnswerContainer(problemType);
const hasSingleAnswer = isSingleAnswerProblem(problemType);
useAnswerContainer({ answers, problemType, updateField });
return (
<div>
<div className="border border-light-700 rounded py-4 pl-4 pr-3">
{answers.map((answer) => (
<AnswerOption
key={answer.id}
@@ -29,7 +32,7 @@ export const AnswersContainer = ({
/>
))}
<Button
className="my-3 ml-2"
className="pl-0 text-primary-500"
iconBefore={Add}
variant="tertiary"
onClick={addAnswer}
@@ -44,6 +47,7 @@ AnswersContainer.propTypes = {
problemType: PropTypes.string.isRequired,
answers: PropTypes.arrayOf(answerOptionProps).isRequired,
addAnswer: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => ({
@@ -52,6 +56,7 @@ export const mapStateToProps = (state) => ({
export const mapDispatchToProps = {
addAnswer: actions.problem.addAnswer,
updateField: actions.problem.updateField,
};
export default connect(mapStateToProps, mapDispatchToProps)(AnswersContainer);

View File

@@ -0,0 +1,90 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { shallow } from 'enzyme';
import { act, render, waitFor } from '@testing-library/react';
import { actions, selectors } from '../../../../../data/redux';
import * as module from './AnswersContainer';
import { AnswersContainer as AnswersContainerWithoutHOC } from './AnswersContainer';
jest.mock('@edx/frontend-platform/i18n', () => ({
FormattedMessage: ({ defaultMessage }) => (<p>{defaultMessage}</p>),
injectIntl: (args) => args,
intlShape: {},
}));
jest.mock('./AnswerOption', () => () => <div>MockAnswerOption</div>);
jest.mock('../../../../../data/redux', () => ({
actions: {
problem: {
updateField: jest.fn().mockName('actions.problem.updateField'),
addAnswer: jest.fn().mockName('actions.problem.addAnswer'),
},
},
selectors: {
problem: {
answers: jest.fn(state => ({ answers: state })),
},
},
}));
describe('AnswersContainer', () => {
const props = {
answers: [],
updateField: jest.fn(),
addAnswer: jest.fn(),
};
describe('render', () => {
test('snapshot: renders correct default', () => {
act(() => {
expect(shallow(<module.AnswersContainer {...props} />)).toMatchSnapshot();
});
});
test('snapshot: renders correctly with answers', () => {
act(() => {
expect(shallow(
<module.AnswersContainer
{...props}
answers={[{ id: 'a', title: 'sOMetITlE', correct: true }, { id: 'b', title: 'sOMetITlE', correct: true }]}
/>,
)).toMatchSnapshot();
});
});
test('with react-testing-library', async () => {
let container = null;
await act(async () => {
const wrapper = render(
<AnswersContainerWithoutHOC
{...props}
answers={[{ id: 'a', title: 'sOMetITlE', correct: true }, { id: 'b', title: 'sOMetITlE', correct: true }]}
/>,
);
container = wrapper.container;
});
await waitFor(() => expect(container.querySelector('button')).toBeTruthy());
await new Promise(resolve => setTimeout(resolve, 500));
expect(props.updateField).toHaveBeenCalledWith(expect.objectContaining({ correctAnswerCount: 2 }));
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('answers from problem.answers', () => {
expect(
module.mapStateToProps(testState).answers,
).toEqual(selectors.problem.answers(testState));
});
});
describe('mapDispatchToProps', () => {
test('updateField from actions.problem.updateField', () => {
expect(module.mapDispatchToProps.updateField).toEqual(actions.problem.updateField);
});
test('updateField from actions.problem.addAnswer', () => {
expect(module.mapDispatchToProps.addAnswer).toEqual(actions.problem.addAnswer);
});
});
});

View File

@@ -2,113 +2,115 @@
exports[`AnswerOption render snapshot: renders correct option with feedback 1`] = `
<Advanced
className="collapsible-card"
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<Row
className="my-2"
<div
className="answer-option-flex-item-1 mr-1 d-flex"
>
<Component
xs={1}
>
<Checker
<Checker
answer={
Object {
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="answer-option-flex-item-2 ml-1"
>
<Form.Control
as="textarea"
autoResize={true}
className="answer-option-textarea text-gray-500 small"
onChange={[Function]}
placeholder="Enter an answer"
rows={1}
value="Answer 1"
/>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
Object {
"correct": true,
"feedback": "some feedback",
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
hasSingleAnswer={false}
intl={
Object {
"formatMessage": [Function],
}
}
problemType="multiplechoiceresponse"
setAnswer={[Function]}
/>
</Component>
<Component
xs={10}
>
<Form.Control
as="textarea"
onChange={[Function]}
placeholder="Enter an answer"
rows={1}
value="Answer 1"
/>
<Body>
<div
className="bg-dark-100 p-4 mt-3"
>
<Form.Group
key="feedback-A"
>
<Form.Label
className="mb-3"
>
<FormattedMessage
defaultMessage="Show following feedback when {answerId} {boldunderline}:"
description="Label text for feedback if option is selected"
id="authoring.answerwidget.feedback.selected.label"
values={
Object {
"answerId": "A",
"boldunderline": <b>
<u>
<FormattedMessage
defaultMessage="is selected"
description="Bold & underlined text for feedback if option is selected"
id="authoring.answerwidget.feedback.selected.label.boldunderline"
/>
</u>
</b>,
}
}
/>
</Form.Label>
<Form.Control
onChange={[Function]}
placeholder="Feedback message"
value="some feedback"
/>
</Form.Group>
</div>
</Body>
</Component>
<Component
className="d-inline-flex mt-1"
xs={1}
>
<Trigger>
<IconButton
alt="Toggle feedback"
iconAs="Icon"
variant="primary"
/>
</Trigger>
</Body>
</div>
<div
className="answer-option-flex-item-3 d-flex flex-row flex-nowrap"
>
<Trigger>
<IconButton
alt="Delete answer"
alt="Toggle feedback"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</Component>
</Row>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with selected unselected feedback 1`] = `
<Advanced
className="collapsible-card"
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<Row
className="my-2"
<div
className="answer-option-flex-item-1 mr-1 d-flex"
>
<Component
xs={1}
>
<Checker
<Checker
answer={
Object {
"correct": true,
"id": "A",
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="answer-option-flex-item-2 ml-1"
>
<Form.Control
as="textarea"
autoResize={true}
className="answer-option-textarea text-gray-500 small"
onChange={[Function]}
placeholder="Enter an answer"
rows={1}
value="Answer 1"
/>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
Object {
"correct": true,
@@ -118,76 +120,32 @@ exports[`AnswerOption render snapshot: renders correct option with selected unse
"unselectedFeedback": "unselected feedback",
}
}
hasSingleAnswer={false}
intl={
Object {
"formatMessage": [Function],
}
}
problemType="choiceresponse"
setAnswer={[Function]}
/>
</Component>
<Component
xs={10}
>
<Form.Control
as="textarea"
onChange={[Function]}
placeholder="Enter an answer"
rows={1}
value="Answer 1"
/>
<Body>
<div
className="bg-dark-100 p-4 mt-3"
>
<Form.Group
key="feedback-A"
>
<Form.Label
className="mb-3"
>
<FormattedMessage
defaultMessage="Show following feedback when {answerId} {boldunderline}:"
description="Label text for feedback if option is selected"
id="authoring.answerwidget.feedback.selected.label"
values={
Object {
"answerId": "A",
"boldunderline": <b>
<u>
<FormattedMessage
defaultMessage="is selected"
description="Bold & underlined text for feedback if option is selected"
id="authoring.answerwidget.feedback.selected.label.boldunderline"
/>
</u>
</b>,
}
}
/>
</Form.Label>
<Form.Control
onChange={[Function]}
placeholder="Feedback message"
/>
</Form.Group>
</div>
</Body>
</Component>
<Component
className="d-inline-flex mt-1"
xs={1}
>
<Trigger>
<IconButton
alt="Toggle feedback"
iconAs="Icon"
variant="primary"
/>
</Trigger>
</Body>
</div>
<div
className="answer-option-flex-item-3 d-flex flex-row flex-nowrap"
>
<Trigger>
<IconButton
alt="Delete answer"
alt="Toggle feedback"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</Component>
</Row>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;

View File

@@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AnswersContainer render snapshot: renders correct default 1`] = `
<div
className="border border-light-700 rounded py-4 pl-4 pr-3"
>
<Button
className="pl-0 text-primary-500"
onClick={[MockFunction]}
variant="tertiary"
>
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Button>
</div>
`;
exports[`AnswersContainer render snapshot: renders correctly with answers 1`] = `
<div
className="border border-light-700 rounded py-4 pl-4 pr-3"
>
<Component
answer={
Object {
"correct": true,
"id": "a",
"title": "sOMetITlE",
}
}
hasSingleAnswer={false}
key="a"
/>
<Component
answer={
Object {
"correct": true,
"id": "b",
"title": "sOMetITlE",
}
}
hasSingleAnswer={false}
key="b"
/>
<Button
className="pl-0 text-primary-500"
onClick={[MockFunction]}
variant="tertiary"
>
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Button>
</div>
`;

View File

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Checker component with multiple answers 1`] = `
<Form.Checkbox
checked={true}
className="pt-2.5"
onChange={[Function]}
value="A"
>
A
</Form.Checkbox>
`;
exports[`Checker component with single answer 1`] = `
<Radio
checked={true}
className="pt-2.5"
onChange={[Function]}
value="A"
>
A
</Radio>
`;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form } from '@edx/paragon';
const Checker = ({
hasSingleAnswer, answer, setAnswer,
}) => {
let CheckerType = Form.Checkbox;
if (hasSingleAnswer) {
CheckerType = Form.Radio;
}
return (
<CheckerType
className="pt-2.5"
value={answer.id}
onChange={(e) => setAnswer({ correct: e.target.checked })}
checked={answer.correct}
>
{answer.id}
</CheckerType>
);
};
Checker.propTypes = {
hasSingleAnswer: PropTypes.bool.isRequired,
answer: PropTypes.shape({
correct: PropTypes.bool,
id: PropTypes.number,
}).isRequired,
setAnswer: PropTypes.func.isRequired,
};
export default Checker;

View File

@@ -0,0 +1,22 @@
import { shallow } from 'enzyme';
import Checker from '.';
const props = {
hasSingleAnswer: true,
answer: {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'some feedback',
},
setAnswer: jest.fn(),
};
describe('Checker component', () => {
test('with single answer', () => {
expect(shallow(<Checker {...props} />)).toMatchSnapshot();
});
test('with multiple answers', () => {
expect(shallow(<Checker {...props} hasSingleAnswer={false} />)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { answerOptionProps } from '../../../../../../../data/services/cms/types';
import FeedbackControl from './FeedbackControl';
import { messages } from './messages';
export const FeedbackBox = ({
answer, setAnswer, intl,
}) => {
const props = {
onChange: (e) => setAnswer({ selectedFeedback: e.target.value }),
answer,
intl,
};
return (
<div className="bg-light-300 p-4 mt-3 rounded">
<FeedbackControl
key={`selectedfeedback-${answer.id}`}
feedback={answer.selectedFeedback}
labelMessage={messages.selectedFeedbackLabel}
labelMessageBoldUnderline={messages.selectedFeedbackLabelBoldUnderlineText}
{...props}
/>
<FeedbackControl
key={`unselectedfeedback-${answer.id}`}
feedback={answer.unselectedFeedback}
labelMessage={messages.unSelectedFeedbackLabel}
labelMessageBoldUnderline={messages.unSelectedFeedbackLabelBoldUnderlineText}
{...props}
/>
</div>
);
};
FeedbackBox.propTypes = {
answer: answerOptionProps.isRequired,
setAnswer: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};
export default injectIntl(FeedbackBox);

View File

@@ -0,0 +1,22 @@
import { shallow } from 'enzyme';
import { FeedbackBox } from './FeedbackBox';
const answerWithFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'some feedback',
unselectedFeedback: 'unselectedFeedback',
};
const props = {
answer: answerWithFeedback,
intl: {},
setAnswer: jest.fn(),
};
describe('FeedbackBox component', () => {
test('renders', () => {
expect(shallow(<FeedbackBox {...props} />)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import { answerOptionProps } from '../../../../../../../data/services/cms/types';
import messages from './messages';
const FeedbackControl = ({
feedback, onChange, labelMessage, labelMessageBoldUnderline, answer, intl,
}) => (
<Form.Group>
<Form.Label className="mb-3">
<FormattedMessage
{...labelMessage}
values={{
answerId: answer.id,
boldunderline: <b><u><FormattedMessage {...labelMessageBoldUnderline} /></u></b>,
}}
/>
</Form.Label>
<Form.Control
placeholder={intl.formatMessage(messages.feedbackPlaceholder)}
value={feedback}
onChange={onChange}
/>
</Form.Group>
);
FeedbackControl.propTypes = {
feedback: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
labelMessage: PropTypes.string.isRequired,
labelMessageBoldUnderline: PropTypes.string.isRequired,
answer: answerOptionProps.isRequired,
intl: intlShape.isRequired,
};
export default FeedbackControl;

View File

@@ -0,0 +1,26 @@
import { shallow } from 'enzyme';
import FeedbackControl from './FeedbackControl';
const answerWithFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'some feedback',
unselectedFeedback: 'unselectedFeedback',
};
const props = {
answer: answerWithFeedback,
intl: { formatMessage: jest.fn() },
setAnswer: jest.fn(),
feedback: 'feedback',
onChange: jest.fn(),
labelMessage: 'msg',
labelMessageBoldUnderline: 'msg',
};
describe('FeedbackControl component', () => {
test('renders', () => {
expect(shallow(<FeedbackControl {...props} />)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FeedbackBox component renders 1`] = `
<div
className="bg-light-300 p-4 mt-3 rounded"
>
<FeedbackControl
answer={
Object {
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
"unselectedFeedback": "unselectedFeedback",
}
}
feedback="some feedback"
intl={Object {}}
key="selectedfeedback-A"
labelMessage={
Object {
"defaultMessage": "Show following feedback when {answerId} {boldunderline}:",
"description": "Label text for feedback if option is selected",
"id": "authoring.answerwidget.feedback.selected.label",
}
}
labelMessageBoldUnderline={
Object {
"defaultMessage": "is selected",
"description": "Bold & underlined text for feedback if option is selected",
"id": "authoring.answerwidget.feedback.selected.label.boldunderline",
}
}
onChange={[Function]}
/>
<FeedbackControl
answer={
Object {
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
"unselectedFeedback": "unselectedFeedback",
}
}
feedback="unselectedFeedback"
intl={Object {}}
key="unselectedfeedback-A"
labelMessage={
Object {
"defaultMessage": "Show following feedback when {answerId} {boldunderline}:",
"description": "Label text for feedback if option is not selected",
"id": "authoring.answerwidget.feedback.unselected.label",
}
}
labelMessageBoldUnderline={
Object {
"defaultMessage": "is not selected",
"description": "Bold & underlined text for feedback if option is not selected",
"id": "authoring.answerwidget.feedback.unselected.label.boldunderline",
}
}
onChange={[Function]}
/>
</div>
`;

View File

@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FeedbackControl component renders 1`] = `
<Form.Group>
<Form.Label
className="mb-3"
>
<FormattedMessage
0="m"
1="s"
2="g"
values={
Object {
"answerId": "A",
"boldunderline": <b>
<u>
<FormattedMessage
0="m"
1="s"
2="g"
/>
</u>
</b>,
}
}
/>
</Form.Label>
<Form.Control
onChange={[MockFunction]}
value="feedback"
/>
</Form.Group>
`;

View File

@@ -0,0 +1,2 @@
export { default as FeedbackBox } from './FeedbackBox';
export { default as FeedbackControl } from './FeedbackControl';

View File

@@ -0,0 +1,34 @@
export const messages = {
feedbackPlaceholder: {
id: 'authoring.answerwidget.feedback.placeholder',
defaultMessage: 'Feedback message',
description: 'Placeholder text for feedback text',
},
feedbackToggleIconAltText: {
id: 'authoring.answerwidget.feedback.icon.alt',
defaultMessage: 'Toggle feedback',
description: 'Alt text for feedback toggle icon',
},
selectedFeedbackLabel: {
id: 'authoring.answerwidget.feedback.selected.label',
defaultMessage: 'Show following feedback when {answerId} {boldunderline}:',
description: 'Label text for feedback if option is selected',
},
selectedFeedbackLabelBoldUnderlineText: {
id: 'authoring.answerwidget.feedback.selected.label.boldunderline',
defaultMessage: 'is selected',
description: 'Bold & underlined text for feedback if option is selected',
},
unSelectedFeedbackLabel: {
id: 'authoring.answerwidget.feedback.unselected.label',
defaultMessage: 'Show following feedback when {answerId} {boldunderline}:',
description: 'Label text for feedback if option is not selected',
},
unSelectedFeedbackLabelBoldUnderlineText: {
id: 'authoring.answerwidget.feedback.unselected.label.boldunderline',
defaultMessage: 'is not selected',
description: 'Bold & underlined text for feedback if option is not selected',
},
};
export default messages;

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { MockUseState } from '../../../../../../testUtils';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import * as module from './hooks';
jest.mock('react', () => {
@@ -52,4 +53,15 @@ describe('Answer Options Hooks', () => {
expect(state.setState[key]).toHaveBeenCalledWith(true);
});
});
describe('isSingleAnswerProblem()', () => {
test('singleSelect', () => {
expect(module.isSingleAnswerProblem(ProblemTypeKeys.SINGLESELECT)).toBe(true);
});
test('multiSelect', () => {
expect(module.isSingleAnswerProblem(ProblemTypeKeys.MULTISELECT)).toBe(false);
});
test('dropdown', () => {
expect(module.isSingleAnswerProblem(ProblemTypeKeys.DROPDOWN)).toBe(true);
});
});
});

View File

@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { StrictDict } from '../../../../../utils';
import * as module from './hooks';
import { actions } from '../../../../../data/redux';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
export const state = StrictDict({
isFeedbackVisible: (val) => useState(val),
@@ -37,6 +38,22 @@ export const prepareFeedback = (answer) => {
};
};
export default {
state, removeAnswer, setAnswer, prepareFeedback,
export const isSingleAnswerProblem = (problemType) => (
problemType === ProblemTypeKeys.DROPDOWN || problemType === ProblemTypeKeys.SINGLESELECT
);
export const useAnswerContainer = ({ answers, updateField }) => {
useEffect(() => {
let answerCount = 0;
answers.forEach(answer => {
if (answer.correct) {
answerCount += 1;
}
});
updateField({ correctAnswerCount: answerCount });
}, []);
};
export default {
state, removeAnswer, setAnswer, prepareFeedback, isSingleAnswerProblem, useAnswerContainer,
};

View File

@@ -15,11 +15,11 @@ const AnswerWidget = ({
const problemStaticData = ProblemTypes[problemType];
return (
<div>
<div className="problem-answer">
<div className="problem-answer-title">
<div className="mt-4 mb-3 text-primary-500">
<div className="h4">
<FormattedMessage {...messages.answerWidgetTitle} />
</div>
<div className="problem-answer-description">
<div className="small">
{problemStaticData.description}
</div>
</div>

View File

@@ -1,11 +1,32 @@
.problem-answer {
padding: 12px;
color: #00262B;
.problem-answer-title {
font-weight: bold;
}
.answer-option {
.answer-option-flex-item-1 {
flex-basis: 8.33%;
}
.problem-answer-description {
font-size: 0.9rem;
.answer-option-flex-item-2 {
flex-basis: 83.34%;
}
.answer-option-flex-item-3 {
flex-basis: 8.33%;
}
.answer-option-textarea {
textarea {
border: none;
resize: none;
&:active, &:hover, &:focus, &:focus-visible {
border: none;
border-right: none;
border-left: none;
border-radius: 0;
box-shadow: none;
}
&:focus, &:hover {
border-bottom: 1px solid #000;
}
}
}
}

View File

@@ -6,6 +6,7 @@ import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import * as hooks from '../../../hooks';
import { selectors, actions } from '../../../../../data/redux';
import { messages } from './messages';
import './index.scss';
// This widget should be connected, grab all questions from store, update them as needed.
export const QuestionWidget = ({
@@ -15,12 +16,11 @@ export const QuestionWidget = ({
const { editorRef, refReady, setEditorRef } = hooks.prepareEditorRef();
if (!refReady) { return null; }
return (
<div>
<div>
<h1>
<FormattedMessage {...messages.questionWidgetTitle} />
</h1>
<Editor {
<div className="question-widget">
<div className="h4">
<FormattedMessage {...messages.questionWidgetTitle} />
</div>
<Editor {
...hooks.problemEditorConfig({
setEditorRef,
editorRef,
@@ -28,8 +28,7 @@ export const QuestionWidget = ({
updateQuestion,
})
}
/>
</div>
/>
</div>
);
};

View File

@@ -0,0 +1,28 @@
.question-widget {
.tox-tinymce {
border-radius: 0.375rem;
}
.tox {
.tox-toolbar__primary {
background: none;
}
}
.tox-statusbar {
border-top: none;
}
.tox-toolbar__group:not(:last-of-type) {
// TODO: Find a way to override the border without !important
border-right: none !important;
&:after {
content: '';
position: relative;
left: 5px;
border: 1px solid #eae6e5;
height: 24px;
}
}
}

View File

@@ -12,7 +12,7 @@ export const SettingsOption = ({
const { isCardCollapsed, toggleCardCollapse } = showFullCard();
return (
<Card>
<Card className="border border-light-700 shadow-none">
<Card.Section className="settingsCardTitleSection">
<Collapsible.Advanced
open={isCardCollapsed}

View File

@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SettingsOption render snapshot: renders correct 1`] = `
<Card>
<Card
className="border border-light-700 shadow-none"
>
<Card.Section
className="settingsCardTitleSection"
>

View File

@@ -2,172 +2,182 @@
exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = `
<div>
<div>
<h3>
<FormattedMessage
defaultMessage="Settings"
description="Settings Title"
id="authoring.problemeditor.settings.settingsWidgetTitle"
/>
</h3>
<Container>
<Row>
<Component>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent)
problemType="stringresponse"
/>
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="mt-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row>
<Advanced
open={true}
>
<Body
className="collapsible-body"
>
<Button
className="my-3 ml-2"
size="inline"
variant="link"
>
<FormattedMessage
defaultMessage="Show advanced settings"
description="Button text to show advanced settings"
id="authoring.problemeditor.settings.showAdvancedButton"
/>
</Button>
</Body>
</Advanced>
</Row>
<Advanced
open={false}
>
<Body
className="collapsible-body"
>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
</Body>
</Advanced>
</Component>
</Row>
</Container>
<div
className="h4"
>
<FormattedMessage
defaultMessage="Settings"
description="Settings Title"
id="authoring.problemeditor.settings.settingsWidgetTitle"
/>
</div>
</div>
`;
exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced settings visible 1`] = `
<div>
<div>
<h3>
<FormattedMessage
defaultMessage="Settings"
description="Settings Title"
id="authoring.problemeditor.settings.settingsWidgetTitle"
/>
</h3>
<Container>
<Row>
<Component>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent)
problemType="stringresponse"
/>
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="mt-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row>
<Advanced
open={false}
>
<Body
className="collapsible-body"
>
<Button
className="my-3 ml-2"
size="inline"
variant="link"
>
<FormattedMessage
defaultMessage="Show advanced settings"
description="Button text to show advanced settings"
id="authoring.problemeditor.settings.showAdvancedButton"
/>
</Button>
</Body>
</Advanced>
</Row>
<Container>
<Row>
<Col>
<Row
className="mb-2"
>
<injectIntl(ShimmedIntlComponent)
problemType="stringresponse"
/>
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="mt-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row>
<Advanced
open={true}
>
<Body
className="collapsible-body"
>
<Row
className="my-2"
<Button
className="my-3 ml-2"
size="inline"
variant="link"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<FormattedMessage
defaultMessage="Show advanced settings"
description="Button text to show advanced settings"
id="authoring.problemeditor.settings.showAdvancedButton"
/>
</Button>
</Body>
</Advanced>
</Component>
</Row>
</Container>
</div>
</Row>
<Advanced
open={false}
>
<Body
className="collapsible-body"
>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
</Body>
</Advanced>
</Col>
</Row>
</Container>
</div>
`;
exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced settings visible 1`] = `
<div>
<div
className="h4"
>
<FormattedMessage
defaultMessage="Settings"
description="Settings Title"
id="authoring.problemeditor.settings.settingsWidgetTitle"
/>
</div>
<Container>
<Row>
<Col>
<Row
className="mb-2"
>
<injectIntl(ShimmedIntlComponent)
problemType="stringresponse"
/>
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="mt-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row>
<Advanced
open={false}
>
<Body
className="collapsible-body"
>
<Button
className="my-3 ml-2"
size="inline"
variant="link"
>
<FormattedMessage
defaultMessage="Show advanced settings"
description="Button text to show advanced settings"
id="authoring.problemeditor.settings.showAdvancedButton"
/>
</Button>
</Body>
</Advanced>
</Row>
<Advanced
open={true}
>
<Body
className="collapsible-body"
>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
<Row
className="my-2"
>
<injectIntl(ShimmedIntlComponent) />
</Row>
</Body>
</Advanced>
</Col>
</Row>
</Container>
</div>
`;

View File

@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
import _ from 'lodash-es';
import * as module from './hooks';
import messages from './messages';
import { ShowAnswerTypesKeys } from '../../../../../data/constants/problem';
import { ProblemTypeKeys, ShowAnswerTypesKeys } from '../../../../../data/constants/problem';
export const state = {
showAdvanced: (val) => useState(val),
@@ -113,14 +113,14 @@ export const resetCardHooks = (updateSettings) => {
export const scoringCardHooks = (scoring, updateSettings) => {
const handleMaxAttemptChange = (event) => {
let unlimitedAttempts = true;
let unlimitedAttempts = false;
let attemptNumber = parseInt(event.target.value);
if (_.isNaN(attemptNumber)) {
attemptNumber = null;
unlimitedAttempts = true;
} else if (attemptNumber < 0) {
attemptNumber = 0;
}
if (attemptNumber > 0) {
unlimitedAttempts = false;
}
updateSettings({ scoring: { ...scoring, attempts: { number: attemptNumber, unlimited: unlimitedAttempts } } });
};
@@ -182,11 +182,23 @@ export const timerCardHooks = (updateSettings) => ({
},
});
export const typeRowHooks = (typeKey, updateField) => {
export const typeRowHooks = ({
answers,
correctAnswerCount,
typeKey,
updateField,
updateAnswer,
}) => {
const onClick = () => {
if (typeKey === ProblemTypeKeys.SINGLESELECT || typeKey === ProblemTypeKeys.DROPDOWN) {
if (correctAnswerCount > 1) {
answers.forEach(answer => {
updateAnswer({ ...answer, correct: false });
});
}
}
updateField({ problemType: typeKey });
};
return {
onClick,
};

View File

@@ -18,6 +18,7 @@ jest.mock('../../../../../data/redux', () => ({
problem: {
updateSettings: (args) => ({ updateSettings: args }),
updateField: (args) => ({ updateField: args }),
updateAnswer: (args) => ({ updateAnswer: args }),
},
},
}));
@@ -177,7 +178,31 @@ describe('Problem settings hooks', () => {
const value = 0;
output.handleMaxAttemptChange({ target: { value } });
expect(updateSettings)
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: value, unlimited: true } } });
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: value, unlimited: false } } });
});
test('test handleMaxAttemptChange set attempts to null value', () => {
const value = null;
output.handleMaxAttemptChange({ target: { value } });
expect(updateSettings)
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: true } } });
});
test('test handleMaxAttemptChange set attempts to empty string', () => {
const value = '';
output.handleMaxAttemptChange({ target: { value } });
expect(updateSettings)
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: true } } });
});
test('test handleMaxAttemptChange set attempts to non-numeric value', () => {
const value = 'abc';
output.handleMaxAttemptChange({ target: { value } });
expect(updateSettings)
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: true } } });
});
test('test handleMaxAttemptChange set attempts to negative value', () => {
const value = -1;
output.handleMaxAttemptChange({ target: { value } });
expect(updateSettings)
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 0, unlimited: false } } });
});
test('test handleWeightChange', () => {
const value = 2;
@@ -217,10 +242,27 @@ describe('Problem settings hooks', () => {
describe('Type row hooks', () => {
test('test onClick', () => {
const typekey = 'TEXTINPUT';
const typekey = 'multiplechoiceresponse';
const updateField = jest.fn();
output = hooks.typeRowHooks(typekey, updateField);
const updateAnswer = jest.fn();
const answers = [{
correct: true,
id: 'a',
},
{
correct: true,
id: 'b',
}];
output = hooks.typeRowHooks({
answers,
correctAnswerCount: 2,
typeKey: typekey,
updateField,
updateAnswer,
});
output.onClick();
expect(updateAnswer).toHaveBeenNthCalledWith(1, { ...answers[0], correct: false });
expect(updateAnswer).toHaveBeenNthCalledWith(2, { ...answers[1], correct: false });
expect(updateField).toHaveBeenCalledWith({ problemType: typekey });
});
});

View File

@@ -13,6 +13,7 @@ import ResetCard from './settingsComponents/ResetCard';
import MatlabCard from './settingsComponents/MatlabCard';
import TimerCard from './settingsComponents/TimerCard';
import TypeCard from './settingsComponents/TypeCard';
import SwitchToAdvancedEditorCard from './settingsComponents/SwitchToAdvancedEditorCard';
import messages from './messages';
import { showAdvancedSettingsCards } from './hooks';
@@ -22,72 +23,90 @@ import './index.scss';
export const SettingsWidget = ({
problemType,
// redux
answers,
correctAnswerCount,
settings,
updateSettings,
updateField,
updateAnswer,
}) => {
const { isAdvancedCardsVisible, showAdvancedCards } = showAdvancedSettingsCards();
return (
<div>
<div>
<h3>
<FormattedMessage {...messages.settingsWidgetTitle} />
</h3>
<Container>
<Row>
<Col>
<Row className="my-2">
<TypeCard problemType={problemType} updateField={updateField} />
</Row>
<Row className="my-2">
<ScoringCard scoring={settings.scoring} updateSettings={updateSettings} />
</Row>
<Row className="mt-2">
<HintsCard hints={settings.hints} updateSettings={updateSettings} />
</Row>
<div className="h4">
<FormattedMessage {...messages.settingsWidgetTitle} />
</div>
<Container>
<Row>
<Col>
<Row className="mb-2">
<TypeCard
answers={answers}
correctAnswerCount={correctAnswerCount}
problemType={problemType}
updateField={updateField}
updateAnswer={updateAnswer}
/>
</Row>
<Row className="my-2">
<ScoringCard scoring={settings.scoring} updateSettings={updateSettings} />
</Row>
<Row className="mt-2">
<HintsCard hints={settings.hints} updateSettings={updateSettings} />
</Row>
<Row>
<Collapsible.Advanced open={!isAdvancedCardsVisible}>
<Collapsible.Body className="collapsible-body">
<Button
className="my-3 ml-2"
variant="link"
size="inline"
onClick={showAdvancedCards}
>
<FormattedMessage {...messages.showAdvanceSettingsButtonText} />
</Button>
</Collapsible.Body>
</Collapsible.Advanced>
</Row>
<Collapsible.Advanced open={isAdvancedCardsVisible}>
<Row>
<Collapsible.Advanced open={!isAdvancedCardsVisible}>
<Collapsible.Body className="collapsible-body">
<Row className="my-2">
<ShowAnswerCard showAnswer={settings.showAnswer} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<ResetCard showResetButton={settings.showResetButton} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<TimerCard timeBetween={settings.timeBetween} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<MatlabCard matLabApiKey={settings.matLabApiKey} updateSettings={updateSettings} />
</Row>
<Button
className="my-3 ml-2"
variant="link"
size="inline"
onClick={showAdvancedCards}
>
<FormattedMessage {...messages.showAdvanceSettingsButtonText} />
</Button>
</Collapsible.Body>
</Collapsible.Advanced>
</Col>
</Row>
</Container>
</div>
</Row>
<Collapsible.Advanced open={isAdvancedCardsVisible}>
<Collapsible.Body className="collapsible-body">
<Row className="my-2">
<ShowAnswerCard showAnswer={settings.showAnswer} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<ResetCard showResetButton={settings.showResetButton} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<TimerCard timeBetween={settings.timeBetween} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<MatlabCard matLabApiKey={settings.matLabApiKey} updateSettings={updateSettings} />
</Row>
<Row className="my-2">
<SwitchToAdvancedEditorCard />
</Row>
</Collapsible.Body>
</Collapsible.Advanced>
</Col>
</Row>
</Container>
</div>
);
};
SettingsWidget.propTypes = {
answers: PropTypes.arrayOf(PropTypes.shape({
correct: PropTypes.bool,
id: PropTypes.string,
selectedFeedback: PropTypes.string,
title: PropTypes.string,
unselectedFeedback: PropTypes.string,
})).isRequired,
correctAnswerCount: PropTypes.number.isRequired,
problemType: PropTypes.string.isRequired,
updateAnswer: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
updateSettings: PropTypes.func.isRequired,
// eslint-disable-next-line
@@ -96,11 +115,14 @@ SettingsWidget.propTypes = {
const mapStateToProps = (state) => ({
settings: selectors.problem.settings(state),
answers: selectors.problem.answers(state),
correctAnswerCount: selectors.problem.correctAnswerCount(state),
});
export const mapDispatchToProps = {
updateSettings: actions.problem.updateSettings,
updateField: actions.problem.updateField,
updateAnswer: actions.problem.updateAnswer,
};
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SettingsWidget));

View File

@@ -36,7 +36,7 @@ export const messages = {
},
noHintSummary: {
id: 'authoring.problemeditor.settings.hint.noHintSummary',
defaultMessage: 'No Hints',
defaultMessage: 'None',
description: 'Summary text for no hints',
},
hintSummary: {
@@ -104,10 +104,35 @@ export const messages = {
defaultMessage: 'Points',
description: 'Scoring weight input label',
},
scoringSummary: {
id: 'authoring.problemeditor.settings.scoring.summary',
defaultMessage: '{attempts, plural, =0 {Unlimited} other {#}} attempts - {weight, plural, =0 {Ungraded} other {# points}}',
description: 'Summary text for scoring settings',
unlimitedAttemptsSummary: {
id: 'authoring.problemeditor.settings.scoring.unlimited',
defaultMessage: 'Unlimited attempts',
description: 'Summary text for unlimited attempts',
},
attemptsSummary: {
id: 'authoring.problemeditor.settings.scoring.attempts',
defaultMessage: '{attempts, plural, =1 {# attempt} other {# attempts}}',
description: 'Summary text for number of attempts',
},
weightSummary: {
id: 'authoring.problemeditor.settings.scoring.weight',
defaultMessage: '{weight, plural, =0 {Ungraded} other {# points}}',
description: 'Summary text for scoring weight',
},
scoringSettingsLabel: {
id: 'authoring.problemeditor.settings.scoring.label',
defaultMessage: 'Specify point weight and the number of answer attempts',
description: 'Descriptive text for scoring settings',
},
attemptsHint: {
id: 'authoring.problemeditor.settings.scoring.attempts.hint',
defaultMessage: 'If a value is not set, unlimited attempts are allowed',
description: 'Summary text for scoring weight',
},
weightHint: {
id: 'authoring.problemeditor.settings.scoring.weight.hint',
defaultMessage: 'If a value is not set, the problem is worth one point',
description: 'Summary text for scoring weight',
},
showAnswerSettingsTitle: {
id: 'authoring.problemeditor.settings.showAnswer.title',
@@ -149,6 +174,26 @@ export const messages = {
defaultMessage: 'Type',
description: 'Type settings card title',
},
SwitchButtonLabel: {
id: 'authoring.problemeditor.settings.switchtoadvancededitor.label',
defaultMessage: 'Switch To Advanced Editor',
description: 'button to switch to the advanced mode of the editor.',
},
ConfirmSwitchMessage: {
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
defaultMessage: 'If you use the advanced editor, this problem will be converted to OLX and you will not be able to return to the simple editor.',
description: 'message to confirm that a user wants to use the advanced editor',
},
ConfirmSwitchMessageTitle: {
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
defaultMessage: 'Convert to OLX?',
description: 'message to confirm that a user wants to use the advanced editor',
},
ConfirmSwitchButtonLabel: {
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
defaultMessage: 'Switch To Advanced Editor',
description: 'message to confirm that a user wants to use the advanced editor',
},
};
export default messages;

View File

@@ -29,7 +29,7 @@ export const HintsCard = ({
/>
))}
<Button
className="my-3 ml-2"
className="pl-0 text-primary-500"
iconBefore={Add}
variant="tertiary"
onClick={handleAdd}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import SettingsOption from '../SettingsOption';
import messages from '../messages';
@@ -14,12 +14,23 @@ export const ScoringCard = ({
}) => {
const { handleMaxAttemptChange, handleWeightChange } = scoringCardHooks(scoring, updateSettings);
const getScoringSummary = (attempts, unlimited, weight) => {
let summary = unlimited
? intl.formatMessage(messages.unlimitedAttemptsSummary)
: intl.formatMessage(messages.attemptsSummary, { attempts });
summary += ` ${String.fromCharCode(183)} `;
summary += intl.formatMessage(messages.weightSummary, { weight });
return summary;
};
return (
<SettingsOption
title={intl.formatMessage(messages.scoringSettingsTitle)}
summary={intl.formatMessage(messages.scoringSummary,
{ attempts: scoring.attempts.number, weight: scoring.weight })}
summary={getScoringSummary(scoring.attempts.number, scoring.attempts.unlimited, scoring.weight)}
>
<Form.Label className="mb-4">
<FormattedMessage {...messages.scoringSettingsLabel} />
</Form.Label>
<Form.Group>
<Form.Control
type="number"
@@ -27,6 +38,9 @@ export const ScoringCard = ({
onChange={handleMaxAttemptChange}
floatingLabel={intl.formatMessage(messages.scoringAttemptsInputLabel)}
/>
<Form.Control.Feedback>
<FormattedMessage {...messages.attemptsHint} />
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
@@ -35,6 +49,9 @@ export const ScoringCard = ({
onChange={handleWeightChange}
floatingLabel={intl.formatMessage(messages.scoringWeightInputLabel)}
/>
<Form.Control.Feedback>
<FormattedMessage {...messages.weightHint} />
</Form.Control.Feedback>
</Form.Group>
</SettingsOption>
);

View File

@@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
import { Form, Hyperlink } from '@edx/paragon';
import SettingsOption from '../SettingsOption';
import { ShowAnswerTypes, ShowAnswerTypesKeys } from '../../../../../../data/constants/problem';
import { selectors } from '../../../../../../data/redux';
import messages from '../messages';
import { showAnswerCardHooks } from '../hooks';
@@ -12,6 +14,9 @@ export const ShowAnswerCard = ({
updateSettings,
// inject
intl,
// redux
studioEndpointUrl,
learningContextId,
}) => {
const {
handleShowAnswerChange,
@@ -29,7 +34,7 @@ export const ShowAnswerCard = ({
</span>
</div>
<div className="spacedMessage">
<Hyperlink destination="#" target="_blank">
<Hyperlink destination={`${studioEndpointUrl}/settings/advanced/${learningContextId}`} target="_blank">
<FormattedMessage {...messages.advancedSettingsLinkText} />
</Hyperlink>
</div>
@@ -49,7 +54,7 @@ export const ShowAnswerCard = ({
))}
</Form.Control>
</Form.Group>
{ showAttempts
{showAttempts
&& (
<Form.Group>
<Form.Control
@@ -69,6 +74,15 @@ ShowAnswerCard.propTypes = {
// eslint-disable-next-line
showAnswer: PropTypes.any.isRequired,
updateSettings: PropTypes.func.isRequired,
studioEndpointUrl: PropTypes.string.isRequired,
learningContextId: PropTypes.string.isRequired,
};
export default injectIntl(ShowAnswerCard);
export const mapStateToProps = (state) => ({
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
learningContextId: selectors.app.learningContextId(state),
});
export const mapDispatchToProps = {};
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ShowAnswerCard));

View File

@@ -1,13 +1,23 @@
import React from 'react';
import { shallow } from 'enzyme';
import { formatMessage } from '../../../../../../../testUtils';
import { ShowAnswerCard } from './ShowAnswerCard';
import { selectors } from '../../../../../../data/redux';
import { ShowAnswerCard, mapStateToProps, mapDispatchToProps } from './ShowAnswerCard';
import { showAnswerCardHooks } from '../hooks';
jest.mock('../hooks', () => ({
showAnswerCardHooks: jest.fn(),
}));
jest.mock('../../../../../../data/redux', () => ({
selectors: {
app: {
studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })),
learningContextId: jest.fn(state => ({ learningContextId: state })),
},
},
}));
describe('ShowAnswerCard', () => {
const showAnswer = {
on: 'after_attempts',
@@ -17,7 +27,11 @@ describe('ShowAnswerCard', () => {
};
const props = {
showAnswer,
// injected
intl: { formatMessage },
// redux
studioEndpointUrl: 'SoMEeNDpOinT',
learningContextId: 'sOMEcouRseId',
};
const showAnswerCardHooksProps = {
@@ -39,4 +53,22 @@ describe('ShowAnswerCard', () => {
expect(shallow(<ShowAnswerCard {...props} />)).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('studioEndpointUrl from app.studioEndpointUrl', () => {
expect(
mapStateToProps(testState).studioEndpointUrl,
).toEqual(selectors.app.studioEndpointUrl(testState));
});
test('learningContextId from app.learningContextId', () => {
expect(
mapStateToProps(testState).learningContextId,
).toEqual(selectors.app.learningContextId(testState));
});
});
describe('mapDispatchToProps', () => {
test('equal an empty object', () => {
expect(mapDispatchToProps).toEqual({});
});
});
});

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { connect } from 'react-redux';
import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, Card } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from '../messages';
import { thunkActions } from '../../../../../../data/redux';
import BaseModal from '../../../../../TextEditor/components/BaseModal';
export const SwitchToAdvancedEditorCard = ({
switchToAdvancedEditor,
}) => {
const [isConfirmOpen, setConfirmOpen] = React.useState(false);
return (
<Card className="border border-light-700 shadow-none">
<BaseModal
isOpen={isConfirmOpen}
close={() => { setConfirmOpen(false); }}
title={(<FormattedMessage {...messages.ConfirmSwitchMessageTitle} />)}
confirmAction={(
<Button
onClick={switchToAdvancedEditor}
>
<FormattedMessage {...messages.ConfirmSwitchButtonLabel} />
</Button>
)}
size="md"
>
<FormattedMessage {...messages.ConfirmSwitchMessage} />
</BaseModal>
<Button
className="my-3 ml-2"
variant="link"
size="inline"
onClick={() => { setConfirmOpen(true); }}
>
<FormattedMessage {...messages.SwitchButtonLabel} />
</Button>
</Card>
);
};
SwitchToAdvancedEditorCard.propTypes = {
switchToAdvancedEditor: PropTypes.func.isRequired,
};
export const mapStateToProps = () => ({
});
export const mapDispatchToProps = {
switchToAdvancedEditor: thunkActions.problem.switchToAdvancedEditor,
};
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SwitchToAdvancedEditorCard));

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { shallow } from 'enzyme';
import { SwitchToAdvancedEditorCard } from './SwitchToAdvancedEditorCard';
describe('SwitchToAdvancedEditorCard snapshot', () => {
const mockSwitchToAdvancedEditor = jest.fn().mockName('switchToAdvancedEditor');
test('snapshot: SwitchToAdvancedEditorCard', () => {
expect(
shallow(<SwitchToAdvancedEditorCard switchToAdvancedEditor={mockSwitchToAdvancedEditor} />),
).toMatchSnapshot();
});
});

View File

@@ -7,12 +7,17 @@ import messages from '../messages';
import TypeRow from './TypeRow';
export const TypeCard = ({
answers,
correctAnswerCount,
problemType,
updateField,
updateAnswer,
// inject
intl,
}) => {
const problemTypeKeysArray = Object.values(ProblemTypeKeys);
const problemTypeKeysArray = Object.values(ProblemTypeKeys).filter(key => key !== ProblemTypeKeys.ADVANCED);
if (problemType === ProblemTypeKeys.ADVANCED) { return null; }
return (
<SettingsOption
@@ -21,12 +26,15 @@ export const TypeCard = ({
>
{problemTypeKeysArray.map((typeKey, i) => (
<TypeRow
answers={answers}
correctAnswerCount={correctAnswerCount}
key={typeKey}
typeKey={typeKey}
label={ProblemTypes[typeKey].title}
selected={typeKey !== problemType}
lastRow={(i + 1) === problemTypeKeysArray.length}
updateField={updateField}
updateAnswer={updateAnswer}
/>
))}
</SettingsOption>
@@ -34,9 +42,19 @@ export const TypeCard = ({
};
TypeCard.propTypes = {
intl: intlShape.isRequired,
answers: PropTypes.arrayOf(PropTypes.shape({
correct: PropTypes.bool,
id: PropTypes.string,
selectedFeedback: PropTypes.string,
title: PropTypes.string,
unselectedFeedback: PropTypes.string,
})).isRequired,
correctAnswerCount: PropTypes.number.isRequired,
problemType: PropTypes.string.isRequired,
updateField: PropTypes.func.isRequired,
updateAnswer: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
};
export default injectIntl(TypeCard);

View File

@@ -6,8 +6,12 @@ import { ProblemTypeKeys } from '../../../../../../data/constants/problem';
describe('TypeCard', () => {
const props = {
answers: [],
correctAnswerCount: 0,
problemType: ProblemTypeKeys.TEXTINPUT,
updateField: jest.fn().mockName('args.updateField'),
updateAnswer: jest.fn().mockName('args.updateAnswer'),
// injected
intl: { formatMessage },
};

View File

@@ -5,13 +5,22 @@ import { Check } from '@edx/paragon/icons';
import { typeRowHooks } from '../hooks';
export const TypeRow = ({
answers,
correctAnswerCount,
typeKey,
label,
selected,
lastRow,
updateField,
updateAnswer,
}) => {
const { onClick } = typeRowHooks(typeKey, updateField);
const { onClick } = typeRowHooks({
answers,
correctAnswerCount,
typeKey,
updateField,
updateAnswer,
});
return (
<>
@@ -25,10 +34,19 @@ export const TypeRow = ({
};
TypeRow.propTypes = {
answers: PropTypes.arrayOf(PropTypes.shape({
correct: PropTypes.bool,
id: PropTypes.string,
selectedFeedback: PropTypes.string,
title: PropTypes.string,
unselectedFeedback: PropTypes.string,
})).isRequired,
correctAnswerCount: PropTypes.number.isRequired,
typeKey: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
lastRow: PropTypes.bool.isRequired,
updateAnswer: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
};

View File

@@ -10,11 +10,14 @@ jest.mock('../hooks', () => ({
describe('TypeRow', () => {
const typeKey = 'TEXTINPUT';
const props = {
answers: [],
correctAnswerCount: 0,
typeKey,
label: 'Text Input Problem',
selected: true,
lastRow: false,
updateField: jest.fn().mockName('args.updateField'),
updateAnswer: jest.fn().mockName('args.updateAnswer'),
};
const typeRowHooksProps = {
@@ -26,7 +29,13 @@ describe('TypeRow', () => {
describe('behavior', () => {
it(' calls typeRowHooks when initialized', () => {
shallow(<TypeRow {...props} />);
expect(typeRowHooks).toHaveBeenCalledWith(typeKey, props.updateField);
expect(typeRowHooks).toHaveBeenCalledWith({
answers: props.answers,
correctAnswerCount: props.correctAnswerCount,
typeKey,
updateField: props.updateField,
updateAnswer: props.updateAnswer,
});
});
});

View File

@@ -5,7 +5,7 @@ exports[`HintRow snapshot snapshot: renders hints row 1`] = `
fluid={true}
>
<Row>
<Component
<Col
xs={10}
>
<Form.Group>
@@ -15,8 +15,8 @@ exports[`HintRow snapshot snapshot: renders hints row 1`] = `
value="hint_1"
/>
</Form.Group>
</Component>
<Component
</Col>
<Col
xs={2}
>
<IconButton
@@ -25,7 +25,7 @@ exports[`HintRow snapshot snapshot: renders hints row 1`] = `
onClick={[MockFunction]}
variant="secondary"
/>
</Component>
</Col>
</Row>
</Container>
`;

View File

@@ -20,7 +20,7 @@ exports[`HintsCard snapshot snapshot: renders hints setting card multiple hints
value=""
/>
<Button
className="my-3 ml-2"
className="pl-0 text-primary-500"
onClick={[MockFunction hintsCardHooks.handleAdd]}
variant="tertiary"
>
@@ -35,11 +35,11 @@ exports[`HintsCard snapshot snapshot: renders hints setting card multiple hints
exports[`HintsCard snapshot snapshot: renders hints setting card no hints 1`] = `
<SettingsOption
summary="No Hints"
summary="None"
title="Hints"
>
<Button
className="my-3 ml-2"
className="pl-0 text-primary-500"
onClick={[MockFunction hintsCardHooks.handleAdd]}
variant="tertiary"
>
@@ -65,7 +65,7 @@ exports[`HintsCard snapshot snapshot: renders hints setting card one hint 1`] =
value="hint1"
/>
<Button
className="my-3 ml-2"
className="pl-0 text-primary-500"
onClick={[MockFunction hintsCardHooks.handleAdd]}
variant="tertiary"
>

View File

@@ -2,9 +2,18 @@
exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
<SettingsOption
summary="{attempts, plural, =0 {Unlimited} other {#}} attempts - {weight, plural, =0 {Ungraded} other {# points}}"
summary="{attempts, plural, =1 {# attempt} other {# attempts}} · {weight, plural, =0 {Ungraded} other {# points}}"
title="Scoring"
>
<Form.Label
className="mb-4"
>
<FormattedMessage
defaultMessage="Specify point weight and the number of answer attempts"
description="Descriptive text for scoring settings"
id="authoring.problemeditor.settings.scoring.label"
/>
</Form.Label>
<Form.Group>
<Form.Control
floatingLabel="Attempts"
@@ -12,6 +21,13 @@ exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
type="number"
value={5}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, unlimited attempts are allowed"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.attempts.hint"
/>
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
@@ -20,15 +36,31 @@ exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
type="number"
value={1.5}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, the problem is worth one point"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.weight.hint"
/>
</Form.Control.Feedback>
</Form.Group>
</SettingsOption>
`;
exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] = `
<SettingsOption
summary="{attempts, plural, =0 {Unlimited} other {#}} attempts - {weight, plural, =0 {Ungraded} other {# points}}"
summary="Unlimited attempts · {weight, plural, =0 {Ungraded} other {# points}}"
title="Scoring"
>
<Form.Label
className="mb-4"
>
<FormattedMessage
defaultMessage="Specify point weight and the number of answer attempts"
description="Descriptive text for scoring settings"
id="authoring.problemeditor.settings.scoring.label"
/>
</Form.Label>
<Form.Group>
<Form.Control
floatingLabel="Attempts"
@@ -36,6 +68,13 @@ exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] =
type="number"
value={0}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, unlimited attempts are allowed"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.attempts.hint"
/>
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
@@ -44,15 +83,31 @@ exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] =
type="number"
value={1.5}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, the problem is worth one point"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.weight.hint"
/>
</Form.Control.Feedback>
</Form.Group>
</SettingsOption>
`;
exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`] = `
<SettingsOption
summary="{attempts, plural, =0 {Unlimited} other {#}} attempts - {weight, plural, =0 {Ungraded} other {# points}}"
summary="{attempts, plural, =1 {# attempt} other {# attempts}} · {weight, plural, =0 {Ungraded} other {# points}}"
title="Scoring"
>
<Form.Label
className="mb-4"
>
<FormattedMessage
defaultMessage="Specify point weight and the number of answer attempts"
description="Descriptive text for scoring settings"
id="authoring.problemeditor.settings.scoring.label"
/>
</Form.Label>
<Form.Group>
<Form.Control
floatingLabel="Attempts"
@@ -60,6 +115,13 @@ exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`
type="number"
value={5}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, unlimited attempts are allowed"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.attempts.hint"
/>
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
@@ -68,6 +130,13 @@ exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`
type="number"
value={0}
/>
<Form.Control.Feedback>
<FormattedMessage
defaultMessage="If a value is not set, the problem is worth one point"
description="Summary text for scoring weight"
id="authoring.problemeditor.settings.scoring.weight.hint"
/>
</Form.Control.Feedback>
</Form.Group>
</SettingsOption>
`;

View File

@@ -20,7 +20,7 @@ exports[`ShowAnswerCard snapshot snapshot: show answer setting card 1`] = `
className="spacedMessage"
>
<Hyperlink
destination="#"
destination="SoMEeNDpOinT/settings/advanced/sOMEcouRseId"
target="_blank"
>
<FormattedMessage

View File

@@ -0,0 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwitchToAdvancedEditorCard snapshot snapshot: SwitchToAdvancedEditorCard 1`] = `
<Card
className="border border-light-700 shadow-none"
>
<BaseModal
close={[Function]}
confirmAction={
<Button
onClick={[MockFunction switchToAdvancedEditor]}
>
<FormattedMessage
defaultMessage="Switch To Advanced Editor"
description="message to confirm that a user wants to use the advanced editor"
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
/>
</Button>
}
footerAction={null}
isOpen={false}
size="md"
title={
<FormattedMessage
defaultMessage="Convert to OLX?"
description="message to confirm that a user wants to use the advanced editor"
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
/>
}
>
<FormattedMessage
defaultMessage="If you use the advanced editor, this problem will be converted to OLX and you will not be able to return to the simple editor."
description="message to confirm that a user wants to use the advanced editor"
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
/>
</BaseModal>
<Button
className="my-3 ml-2"
onClick={[Function]}
size="inline"
variant="link"
>
<FormattedMessage
defaultMessage="Switch To Advanced Editor"
description="button to switch to the advanced mode of the editor."
id="authoring.problemeditor.settings.switchtoadvancededitor.label"
/>
</Button>
</Card>
`;

View File

@@ -2,55 +2,62 @@
exports[`TypeCard snapshot snapshot: renders type setting card 1`] = `
<SettingsOption
summary="Text Input Problem"
summary="Text input"
title="Type"
>
<TypeRow
answers={Array []}
correctAnswerCount={0}
key="multiplechoiceresponse"
label="Single Select Problem"
label="Single select"
lastRow={false}
selected={true}
typeKey="multiplechoiceresponse"
updateAnswer={[MockFunction args.updateAnswer]}
updateField={[MockFunction args.updateField]}
/>
<TypeRow
answers={Array []}
correctAnswerCount={0}
key="choiceresponse"
label="Multi Select Problem"
label="Multi-select"
lastRow={false}
selected={true}
typeKey="choiceresponse"
updateAnswer={[MockFunction args.updateAnswer]}
updateField={[MockFunction args.updateField]}
/>
<TypeRow
answers={Array []}
correctAnswerCount={0}
key="optionresponse"
label="Dropdown Problem"
label="Dropdown"
lastRow={false}
selected={true}
typeKey="optionresponse"
updateAnswer={[MockFunction args.updateAnswer]}
updateField={[MockFunction args.updateField]}
/>
<TypeRow
answers={Array []}
correctAnswerCount={0}
key="numericalresponse"
label="Numeric Response Problem"
label="Numerical input"
lastRow={false}
selected={true}
typeKey="numericalresponse"
updateAnswer={[MockFunction args.updateAnswer]}
updateField={[MockFunction args.updateField]}
/>
<TypeRow
answers={Array []}
correctAnswerCount={0}
key="stringresponse"
label="Text Input Problem"
lastRow={false}
label="Text input"
lastRow={true}
selected={false}
typeKey="stringresponse"
updateField={[MockFunction args.updateField]}
/>
<TypeRow
key="advanced"
label="Advanced Problem"
lastRow={true}
selected={true}
typeKey="advanced"
updateAnswer={[MockFunction args.updateAnswer]}
updateField={[MockFunction args.updateField]}
/>
</SettingsOption>

View File

@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditorProblemView component renders raw editor 1`] = `
<injectIntl(ShimmedIntlComponent)
getContent={[Function]}
>
<Container
className="mt-3 px-4"
fluid={true}
>
<Row>
<Col
xs={9}
>
<RawEditor
content={null}
editorRef={
Object {
"current": null,
}
}
lang="xml"
/>
</Col>
<Col
xs={3}
>
<injectIntl(ShimmedIntlComponent)
problemType="advanced"
/>
</Col>
</Row>
</Container>
</injectIntl(ShimmedIntlComponent)>
`;
exports[`EditorProblemView component renders simple view 1`] = `
<injectIntl(ShimmedIntlComponent)
getContent={[Function]}
>
<Container
className="mt-3 px-4"
fluid={true}
>
<Row>
<Col
xs={9}
>
<injectIntl(ShimmedIntlComponent) />
<AnswerWidget
problemType="multiplechoiceresponse"
/>
</Col>
<Col
xs={3}
>
<injectIntl(ShimmedIntlComponent)
problemType="multiplechoiceresponse"
/>
</Col>
</Row>
</Container>
</injectIntl(ShimmedIntlComponent)>
`;

View File

@@ -0,0 +1,14 @@
import ReactStateSettingsParser from '../../data/ReactStateSettingsParser';
import ReactStateOLXParser from '../../data/ReactStateOLXParser';
// eslint-disable-next-line import/prefer-default-export
export const parseState = (problem, isAdvanced, ref) => () => {
const reactSettingsParser = new ReactStateSettingsParser(problem);
const reactOLXParser = new ReactStateOLXParser({ problem });
const rawOLX = ref?.current?.state.doc.toString();
return {
settings: reactSettingsParser.getSettings(),
olx: isAdvanced ? rawOLX : reactOLXParser.buildOLX(),
};
};

View File

@@ -0,0 +1,25 @@
import * as hooks from './hooks';
const mockRawOLX = 'rawOLX';
const mockBuiltOLX = 'builtOLX';
jest.mock('../../data/ReactStateOLXParser', () => (
jest.fn().mockImplementation(() => ({
buildOLX: () => mockBuiltOLX,
}))
));
jest.mock('../../data/ReactStateSettingsParser');
describe('EditProblemView hooks parseState', () => {
const toStringMock = () => mockRawOLX;
const refMock = { current: { state: { doc: { toString: toStringMock } } } };
test('default problem', () => {
const res = hooks.parseState('problem', false, refMock)();
expect(res.olx).toBe(mockBuiltOLX);
});
test('advanced problem', () => {
const res = hooks.parseState('problem', true, refMock)();
expect(res.olx).toBe(mockRawOLX);
});
});

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
@@ -6,34 +6,35 @@ import { Col, Container, Row } from '@edx/paragon';
import AnswerWidget from './AnswerWidget';
import SettingsWidget from './SettingsWidget';
import QuestionWidget from './QuestionWidget';
import { EditorContainer } from '../../../EditorContainer';
import EditorContainer from '../../../EditorContainer';
import { selectors } from '../../../../data/redux';
import ReactStateSettingsParser from '../../data/ReactStateSettingsParser';
import ReactStateOLXParser from '../../data/ReactStateOLXParser';
import { AdvanceProblemKeys } from '../../../../data/constants/problem';
import RawEditor from '../../../../sharedComponents/RawEditor';
import { ProblemTypeKeys } from '../../../../data/constants/problem';
import { parseState } from './hooks';
export const EditProblemView = ({
problemType,
problemState,
}) => {
const parseState = (problem) => () => {
const reactSettingsParser = new ReactStateSettingsParser(problem);
const reactOLXParser = new ReactStateOLXParser({ problem });
return {
settings: reactSettingsParser.getSettings(),
olx: reactOLXParser.buildOLX(),
};
};
if (Object.values(AdvanceProblemKeys).includes(problemType)) {
return `hello raw editor with ${problemType}`;
}
const editorRef = useRef(null);
const isAdvancedProblemType = problemType === ProblemTypeKeys.ADVANCED;
const getContent = parseState(problemState, isAdvancedProblemType, editorRef);
return (
<EditorContainer getContent={parseState(problemState)}>
<Container fluid>
<EditorContainer getContent={getContent}>
<Container fluid className="mt-3 px-4">
<Row>
<Col xs={9}>
<QuestionWidget />
<AnswerWidget problemType={problemType} />
{isAdvancedProblemType ? (
<RawEditor editorRef={editorRef} lang="xml" content={problemState.rawOLX} />
) : (
<>
<QuestionWidget />
<AnswerWidget problemType={problemType} />
</>
)}
</Col>
<Col xs={3}>
<SettingsWidget problemType={problemType} />

View File

@@ -0,0 +1,20 @@
import { shallow } from 'enzyme';
import { EditProblemView } from '.';
import AnswerWidget from './AnswerWidget';
import { ProblemTypeKeys } from '../../../../data/constants/problem';
import RawEditor from '../../../../sharedComponents/RawEditor';
describe('EditorProblemView component', () => {
test('renders simple view', () => {
const wrapper = shallow(<EditProblemView problemType={ProblemTypeKeys.SINGLESELECT} problemState={{}} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find(AnswerWidget).length).toBe(1);
});
test('renders raw editor', () => {
const wrapper = shallow(<EditProblemView problemType={ProblemTypeKeys.ADVANCED} problemState={{}} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find(AnswerWidget).length).toBe(0);
expect(wrapper.find(RawEditor).length).toBe(1);
});
});

View File

@@ -17,12 +17,12 @@ export const SelectTypeFooter = ({
onCancel,
selected,
// redux
setProblemType,
updateField,
setBlockTitle,
// injected,
intl,
}) => (
<div className="editor-footer position-sticky" style={{ bottom: 0 }}>
<div className="editor-footer fixed-bottom">
<ModalDialog.Footer className="border-top-0">
<ActionRow>
<ActionRow.Spacer />
@@ -35,7 +35,7 @@ export const SelectTypeFooter = ({
</Button>
<Button
aria-label={intl.formatMessage(messages.selectButtonAriaLabel)}
onClick={hooks.onSelect(setProblemType, selected, updateField)}
onClick={hooks.onSelect({ selected, updateField, setBlockTitle })}
disabled={!selected}
>
<FormattedMessage {...messages.selectButtonLabel} />
@@ -52,8 +52,8 @@ SelectTypeFooter.defaultProps = {
SelectTypeFooter.propTypes = {
onCancel: PropTypes.func.isRequired,
selected: PropTypes.string,
setProblemType: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
setBlockTitle: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
};
@@ -62,8 +62,8 @@ export const mapStateToProps = () => ({
});
export const mapDispatchToProps = {
setProblemType: actions.problem.setProblemType,
updateField: actions.problem.updateField,
setBlockTitle: actions.app.setBlockTitle,
};
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SelectTypeFooter));

View File

@@ -16,7 +16,7 @@ describe('SelectTypeFooter', () => {
onCancel: jest.fn().mockName('onCancel'),
selected: null,
// redux
setProblemType: jest.fn().mockName('setProblemType'),
updateField: jest.fn().mockName('UpdateField'),
// inject
intl: { formatMessage },
};
@@ -36,7 +36,7 @@ describe('SelectTypeFooter', () => {
.toEqual(expected);
});
test('select behavior is linked to modal onSelect', () => {
const expected = hooks.onSelect(props.setProblemType, props.selected);
const expected = hooks.onSelect(props.selected, props.updateField);
expect(el.find(Button).last().props().onClick)
.toEqual(expected);
});
@@ -48,8 +48,8 @@ describe('SelectTypeFooter', () => {
});
});
describe('mapDispatchToProps', () => {
test('loads setProblemType from problem.setProblemType actions', () => {
expect(module.mapDispatchToProps.setProblemType).toEqual(actions.problem.setProblemType);
test('loads updateField from problem.updateField actions', () => {
expect(module.mapDispatchToProps.updateField).toEqual(actions.problem.updateField);
});
});
});

View File

@@ -2,12 +2,7 @@
exports[`SelectTypeFooter snapshot 1`] = `
<div
className="editor-footer position-sticky"
style={
Object {
"bottom": 0,
}
}
className="editor-footer fixed-bottom"
>
<ModalDialog.Footer
className="border-top-0"

View File

@@ -4,9 +4,11 @@ exports[`SelectTypeWrapper snapshot 1`] = `
<div>
<ModalDialog.Header>
<ModalDialog.Title>
<p>
Select Problem type
</p>
<FormattedMessage
defaultMessage="Select problem type"
description="Title for select problem type modal"
id="authoring.problemEditor.selectType.title"
/>
<div
className="pgn__modal-close-container"
>

View File

@@ -1,29 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Icon, ModalDialog, IconButton } from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import SelectTypeFooter from './SelectTypeFooter';
import * as hooks from '../../../../EditorContainer/hooks';
import messages from './messages';
export const SelectTypeWrapper = ({
children,
onClose,
selected,
}) => {
const handleCancelClicked = hooks.handleCancelClicked({ onClose });
const handleCancel = hooks.handleCancel({ onClose });
return (
<div>
<ModalDialog.Header>
<ModalDialog.Title>
<p>Select Problem type</p>
<FormattedMessage {...messages.selectTypeTitle} />
<div className="pgn__modal-close-container">
<IconButton
src={Close}
iconAs={Icon}
onClick={handleCancelClicked}
onClick={handleCancel}
/>
</div>
</ModalDialog.Title>
@@ -33,7 +34,7 @@ export const SelectTypeWrapper = ({
</ModalDialog.Body>
<SelectTypeFooter
selected={selected}
onCancel={handleCancelClicked}
onCancel={handleCancel}
/>
</div>
);
@@ -48,4 +49,4 @@ SelectTypeWrapper.propTypes = {
onClose: PropTypes.func,
};
export default SelectTypeWrapper;
export default injectIntl(SelectTypeWrapper);

View File

@@ -2,10 +2,10 @@ import React from 'react';
import { shallow } from 'enzyme';
import { IconButton } from '@edx/paragon';
import * as module from '.';
import { handleCancelClicked } from '../../../../EditorContainer/hooks';
import { handleCancel } from '../../../../EditorContainer/hooks';
jest.mock('../../../../EditorContainer/hooks', () => ({
handleCancelClicked: jest.fn().mockName('handleCancelClicked'),
handleCancel: jest.fn().mockName('handleCancel'),
}));
describe('SelectTypeWrapper', () => {
@@ -25,7 +25,7 @@ describe('SelectTypeWrapper', () => {
el = shallow(<module.SelectTypeWrapper {...props} />);
});
test('close behavior is linked to modal onClose', () => {
const expected = handleCancelClicked({ onClose: props.onClose });
const expected = handleCancel({ onClose: props.onClose });
expect(el.find(IconButton).props().onClick)
.toEqual(expected);
});

View File

@@ -1,4 +1,9 @@
export const messages = {
selectTypeTitle: {
id: 'authoring.problemEditor.selectType.title',
defaultMessage: 'Select problem type',
description: 'Title for select problem type modal',
},
cancelButtonLabel: {
id: 'authoring.problemeditor.selecttype.cancelButton.label',
defaultMessage: 'Cancel',

View File

@@ -1,24 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SelectTypeModal snapshot 1`] = `
<SelectTypeWrapper
<injectIntl(ShimmedIntlComponent)
onClose={[MockFunction]}
selected="mOcKsELEcted"
>
<Row
className="justify-content-center align-items-center m-4"
className="justify-content-center"
>
<Component>
<Stack
className="flex-wrap"
direction="horizontal"
gap={4}
>
<injectIntl(ShimmedIntlComponent)
selected="mOcKsELEcted"
setSelected={[MockFunction setSelected]}
/>
</Component>
<Component>
<injectIntl(ShimmedIntlComponent)
problemType="mOcKsELEcted"
/>
</Component>
</Stack>
</Row>
</SelectTypeWrapper>
</injectIntl(ShimmedIntlComponent)>
`;

View File

@@ -8,6 +8,8 @@ import {
Icon,
OverlayTrigger,
Tooltip,
Hyperlink,
Col,
} from '@edx/paragon';
import { ArrowBack } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -22,8 +24,8 @@ export const AdvanceTypeSelect = ({
}) => {
const handleChange = e => { setSelected(e.target.value); };
return (
<div className="col col-8 border rounded p-0 justify-content-center">
<Form.Group className="p-0">
<Col xs={12} md={8} className="justify-content-center">
<Form.Group className="border rounded text-primary-500 p-0">
<ActionRow className="border-primary-100 border-bottom py-3 pl-2.5 pr-4">
<IconButton src={ArrowBack} iconAs={Icon} onClick={() => setSelected(ProblemTypeKeys.SINGLESELECT)} />
<ActionRow.Spacer />
@@ -56,7 +58,7 @@ export const AdvanceTypeSelect = ({
</Tooltip>
)}
>
<div>
<div className="text-gray-500">
{intl.formatMessage(messages.problemSupportStatus, { supportStatus: data.status })}
</div>
</OverlayTrigger>
@@ -74,7 +76,13 @@ export const AdvanceTypeSelect = ({
})}
</Form.RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage {...messages.learnMoreAdvancedButtonLabel} />
</Hyperlink>
</Col>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Hyperlink, Image } from '@edx/paragon';
import { Hyperlink, Image, Container } from '@edx/paragon';
import {
FormattedMessage,
injectIntl,
@@ -19,7 +19,7 @@ export const Preview = ({
}
const data = ProblemTypes[problemType];
return (
<div className="bg-light-300 rounded p-4">
<Container style={{ width: '494px', height: '400px' }} className="bg-light-300 rounded p-4">
<div className="small">
{intl.formatMessage(messages.previewTitle, { previewTitle: data.title })}
</div>
@@ -38,7 +38,7 @@ export const Preview = ({
>
<FormattedMessage {...messages.learnMoreButtonLabel} />
</Hyperlink>
</div>
</Container>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, SelectableBox } from '@edx/paragon';
import { Button, Container, SelectableBox } from '@edx/paragon';
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
import { ProblemTypes, ProblemTypeKeys, AdvanceProblemKeys } from '../../../../../data/constants/problem';
import messages from './messages';
@@ -14,7 +14,7 @@ export const ProblemTypeSelect = ({
const settings = { 'aria-label': 'checkbox', type: 'radio' };
return (
<>
<Container style={{ width: '494px', height: '400px' }}>
<SelectableBox.Set
columns={1}
onChange={handleChange}
@@ -24,7 +24,12 @@ export const ProblemTypeSelect = ({
{Object.values(ProblemTypeKeys).map((key) => (
key !== 'advanced'
? (
<SelectableBox id={key} value={key} {...settings}>
<SelectableBox
className="border border-light-400 text-primary-500 shadow-none"
id={key}
value={key}
{...settings}
>
{ProblemTypes[key].title}
</SelectableBox>
)
@@ -34,7 +39,7 @@ export const ProblemTypeSelect = ({
<Button variant="link" className="pl-0 mt-2" onClick={handleClick}>
<FormattedMessage {...messages.advanceProblemButtonLabel} />
</Button>
</>
</Container>
);
};
ProblemTypeSelect.propTypes = {

View File

@@ -1,11 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default props 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -39,7 +41,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -80,7 +82,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -133,53 +137,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -217,7 +181,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -270,22 +236,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with default
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is circuitschematic 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -319,7 +299,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -360,7 +340,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -413,53 +395,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -497,7 +439,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -550,22 +494,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is customgrader 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -599,7 +557,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -640,7 +598,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -693,53 +653,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -777,7 +697,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -830,22 +752,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is drag_and_drop 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -879,7 +815,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -920,7 +856,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -973,53 +911,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -1057,7 +955,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1110,22 +1010,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is formularesponse 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -1159,7 +1073,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -1200,7 +1114,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1253,53 +1169,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -1337,7 +1213,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1390,22 +1268,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is imageresponse 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -1439,7 +1331,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -1480,7 +1372,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1533,53 +1427,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -1617,7 +1471,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1670,22 +1526,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is jsinput_response 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -1719,7 +1589,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -1760,7 +1630,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1813,53 +1685,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -1897,7 +1729,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -1950,22 +1784,36 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;
exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problemType is problem_with_hint 1`] = `
<div
className="col col-8 border rounded p-0 justify-content-center"
<Col
className="justify-content-center"
md={8}
xs={12}
>
<Form.Group
className="p-0"
className="border rounded text-primary-500 p-0"
>
<ActionRow
className="border-primary-100 border-bottom py-3 pl-2.5 pr-4"
@@ -1999,7 +1847,7 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
id="blankadvanced"
value="blankadvanced"
>
Blank advance problem
Blank advanced problem
</Radio>
<ActionRow.Spacer />
</ActionRow>
@@ -2040,7 +1888,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -2093,53 +1943,13 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Provisional
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
<Radio
id="draganddrop"
value="draganddrop"
>
Drag and drop (deprecated version)
</Radio>
<ActionRow.Spacer />
<OverlayTrigger
overlay={
<Tooltip>
<div
className="text-gray-300 text-left"
>
{supportStatus, select,
Provisional {Provisionally supported tools might lack the robustness of functionality
that your courses require. edX does not have control over the quality of the software,
or of the content that can be provided using these tools.
Test these tools thoroughly before using them in your course, especially in graded
sections. Complete documentstion might not be available for provisionally supported
tools, or documentation might be available from sources other than edX.}
Not_supported {Tools with no support are not maintained by edX, and might be deprecated
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
other { }
}
</div>
</Tooltip>
}
placement="right"
>
<div>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
<ActionRow
className="border-primary-100 border-bottom m-0 py-3 w-100"
>
@@ -2177,7 +1987,9 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
@@ -2230,12 +2042,24 @@ exports[`AdvanceTypeSelect snapshots snapshots: renders as expected with problem
}
placement="right"
>
<div>
<div
className="text-gray-500"
>
Not supported
</div>
</OverlayTrigger>
</ActionRow>
</RadioSet>
</Form.Group>
</div>
<Hyperlink
destination="https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#advanced"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about advanced problem types"
description="Label for Learn more about advanced problem types button"
id="authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label"
/>
</Hyperlink>
</Col>
`;

View File

@@ -3,13 +3,19 @@
exports[`Preview snapshots snapshots: renders as expected with default props 1`] = `""`;
exports[`Preview snapshots snapshots: renders as expected with problemType is choiceresponse 1`] = `
<div
<Container
className="bg-light-300 rounded p-4"
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<div
className="small"
>
Multi Select Problem
Multi-select problem
</div>
<Image
alt="A preview illustration of a {problemType, select,
@@ -34,22 +40,28 @@ exports[`Preview snapshots snapshots: renders as expected with problemType is ch
target="_blank"
>
<FormattedMessage
defaultMessage="Learn More"
description="Label for Learn More button"
defaultMessage="Learn more"
description="Label for Learn more button"
id="authoring.problemEditor.learnMoreButtonLabel.label"
/>
</Hyperlink>
</div>
</Container>
`;
exports[`Preview snapshots snapshots: renders as expected with problemType is multiplechoiceresponse 1`] = `
<div
<Container
className="bg-light-300 rounded p-4"
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<div
className="small"
>
Single Select Problem
Single select problem
</div>
<Image
alt="A preview illustration of a {problemType, select,
@@ -74,22 +86,28 @@ exports[`Preview snapshots snapshots: renders as expected with problemType is mu
target="_blank"
>
<FormattedMessage
defaultMessage="Learn More"
description="Label for Learn More button"
defaultMessage="Learn more"
description="Label for Learn more button"
id="authoring.problemEditor.learnMoreButtonLabel.label"
/>
</Hyperlink>
</div>
</Container>
`;
exports[`Preview snapshots snapshots: renders as expected with problemType is numericalresponse 1`] = `
<div
<Container
className="bg-light-300 rounded p-4"
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<div
className="small"
>
Numeric Response Problem
Numerical input problem
</div>
<Image
alt="A preview illustration of a {problemType, select,
@@ -114,22 +132,28 @@ exports[`Preview snapshots snapshots: renders as expected with problemType is nu
target="_blank"
>
<FormattedMessage
defaultMessage="Learn More"
description="Label for Learn More button"
defaultMessage="Learn more"
description="Label for Learn more button"
id="authoring.problemEditor.learnMoreButtonLabel.label"
/>
</Hyperlink>
</div>
</Container>
`;
exports[`Preview snapshots snapshots: renders as expected with problemType is optionresponse 1`] = `
<div
<Container
className="bg-light-300 rounded p-4"
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<div
className="small"
>
Dropdown Problem
Dropdown problem
</div>
<Image
alt="A preview illustration of a {problemType, select,
@@ -154,22 +178,28 @@ exports[`Preview snapshots snapshots: renders as expected with problemType is op
target="_blank"
>
<FormattedMessage
defaultMessage="Learn More"
description="Label for Learn More button"
defaultMessage="Learn more"
description="Label for Learn more button"
id="authoring.problemEditor.learnMoreButtonLabel.label"
/>
</Hyperlink>
</div>
</Container>
`;
exports[`Preview snapshots snapshots: renders as expected with problemType is stringresponse 1`] = `
<div
<Container
className="bg-light-300 rounded p-4"
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<div
className="small"
>
Text Input Problem
Text input problem
</div>
<Image
alt="A preview illustration of a {problemType, select,
@@ -194,10 +224,10 @@ exports[`Preview snapshots snapshots: renders as expected with problemType is st
target="_blank"
>
<FormattedMessage
defaultMessage="Learn More"
description="Label for Learn More button"
defaultMessage="Learn more"
description="Label for Learn more button"
id="authoring.problemEditor.learnMoreButtonLabel.label"
/>
</Hyperlink>
</div>
</Container>
`;

View File

@@ -1,7 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProblemTypeSelect snapshot DROPDOWN 1`] = `
<Fragment>
<Container
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<SelectableBox.Set
columns={1}
onChange={[Function]}
@@ -10,43 +17,48 @@ exports[`ProblemTypeSelect snapshot DROPDOWN 1`] = `
>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="multiplechoiceresponse"
type="radio"
value="multiplechoiceresponse"
>
Single Select Problem
Single select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="choiceresponse"
type="radio"
value="choiceresponse"
>
Multi Select Problem
Multi-select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="optionresponse"
type="radio"
value="optionresponse"
>
Dropdown Problem
Dropdown
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="numericalresponse"
type="radio"
value="numericalresponse"
>
Numeric Response Problem
Numerical input
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="stringresponse"
type="radio"
value="stringresponse"
>
Text Input Problem
Text input
</SelectableBox>
</SelectableBox.Set>
<Button
@@ -55,16 +67,23 @@ exports[`ProblemTypeSelect snapshot DROPDOWN 1`] = `
variant="link"
>
<FormattedMessage
defaultMessage="Advance problem types"
defaultMessage="Advanced problem types"
description="Button label for advance problem types option"
id="authoring.problemEditor.problemSelect.advanceButton.label"
/>
</Button>
</Fragment>
</Container>
`;
exports[`ProblemTypeSelect snapshot MULTISELECT 1`] = `
<Fragment>
<Container
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<SelectableBox.Set
columns={1}
onChange={[Function]}
@@ -73,43 +92,48 @@ exports[`ProblemTypeSelect snapshot MULTISELECT 1`] = `
>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="multiplechoiceresponse"
type="radio"
value="multiplechoiceresponse"
>
Single Select Problem
Single select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="choiceresponse"
type="radio"
value="choiceresponse"
>
Multi Select Problem
Multi-select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="optionresponse"
type="radio"
value="optionresponse"
>
Dropdown Problem
Dropdown
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="numericalresponse"
type="radio"
value="numericalresponse"
>
Numeric Response Problem
Numerical input
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="stringresponse"
type="radio"
value="stringresponse"
>
Text Input Problem
Text input
</SelectableBox>
</SelectableBox.Set>
<Button
@@ -118,16 +142,23 @@ exports[`ProblemTypeSelect snapshot MULTISELECT 1`] = `
variant="link"
>
<FormattedMessage
defaultMessage="Advance problem types"
defaultMessage="Advanced problem types"
description="Button label for advance problem types option"
id="authoring.problemEditor.problemSelect.advanceButton.label"
/>
</Button>
</Fragment>
</Container>
`;
exports[`ProblemTypeSelect snapshot NUMERIC 1`] = `
<Fragment>
<Container
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<SelectableBox.Set
columns={1}
onChange={[Function]}
@@ -136,43 +167,48 @@ exports[`ProblemTypeSelect snapshot NUMERIC 1`] = `
>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="multiplechoiceresponse"
type="radio"
value="multiplechoiceresponse"
>
Single Select Problem
Single select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="choiceresponse"
type="radio"
value="choiceresponse"
>
Multi Select Problem
Multi-select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="optionresponse"
type="radio"
value="optionresponse"
>
Dropdown Problem
Dropdown
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="numericalresponse"
type="radio"
value="numericalresponse"
>
Numeric Response Problem
Numerical input
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="stringresponse"
type="radio"
value="stringresponse"
>
Text Input Problem
Text input
</SelectableBox>
</SelectableBox.Set>
<Button
@@ -181,16 +217,23 @@ exports[`ProblemTypeSelect snapshot NUMERIC 1`] = `
variant="link"
>
<FormattedMessage
defaultMessage="Advance problem types"
defaultMessage="Advanced problem types"
description="Button label for advance problem types option"
id="authoring.problemEditor.problemSelect.advanceButton.label"
/>
</Button>
</Fragment>
</Container>
`;
exports[`ProblemTypeSelect snapshot SINGLESELECT 1`] = `
<Fragment>
<Container
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<SelectableBox.Set
columns={1}
onChange={[Function]}
@@ -199,43 +242,48 @@ exports[`ProblemTypeSelect snapshot SINGLESELECT 1`] = `
>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="multiplechoiceresponse"
type="radio"
value="multiplechoiceresponse"
>
Single Select Problem
Single select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="choiceresponse"
type="radio"
value="choiceresponse"
>
Multi Select Problem
Multi-select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="optionresponse"
type="radio"
value="optionresponse"
>
Dropdown Problem
Dropdown
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="numericalresponse"
type="radio"
value="numericalresponse"
>
Numeric Response Problem
Numerical input
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="stringresponse"
type="radio"
value="stringresponse"
>
Text Input Problem
Text input
</SelectableBox>
</SelectableBox.Set>
<Button
@@ -244,16 +292,23 @@ exports[`ProblemTypeSelect snapshot SINGLESELECT 1`] = `
variant="link"
>
<FormattedMessage
defaultMessage="Advance problem types"
defaultMessage="Advanced problem types"
description="Button label for advance problem types option"
id="authoring.problemEditor.problemSelect.advanceButton.label"
/>
</Button>
</Fragment>
</Container>
`;
exports[`ProblemTypeSelect snapshot TEXTINPUT 1`] = `
<Fragment>
<Container
style={
Object {
"height": "400px",
"width": "494px",
}
}
>
<SelectableBox.Set
columns={1}
onChange={[Function]}
@@ -262,43 +317,48 @@ exports[`ProblemTypeSelect snapshot TEXTINPUT 1`] = `
>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="multiplechoiceresponse"
type="radio"
value="multiplechoiceresponse"
>
Single Select Problem
Single select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="choiceresponse"
type="radio"
value="choiceresponse"
>
Multi Select Problem
Multi-select
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="optionresponse"
type="radio"
value="optionresponse"
>
Dropdown Problem
Dropdown
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="numericalresponse"
type="radio"
value="numericalresponse"
>
Numeric Response Problem
Numerical input
</SelectableBox>
<SelectableBox
aria-label="checkbox"
className="border border-light-400 text-primary-500 shadow-none"
id="stringresponse"
type="radio"
value="stringresponse"
>
Text Input Problem
Text input
</SelectableBox>
</SelectableBox.Set>
<Button
@@ -307,10 +367,10 @@ exports[`ProblemTypeSelect snapshot TEXTINPUT 1`] = `
variant="link"
>
<FormattedMessage
defaultMessage="Advance problem types"
defaultMessage="Advanced problem types"
description="Button label for advance problem types option"
id="authoring.problemEditor.problemSelect.advanceButton.label"
/>
</Button>
</Fragment>
</Container>
`;

View File

@@ -1,7 +1,7 @@
export const messages = {
advanceProblemButtonLabel: {
id: 'authoring.problemEditor.problemSelect.advanceButton.label',
defaultMessage: 'Advance problem types',
defaultMessage: 'Advanced problem types',
description: 'Button label for advance problem types option',
},
advanceMenuTitle: {
@@ -39,7 +39,7 @@ export const messages = {
},
previewTitle: {
id: 'authoring.problemEditor.preview.title',
defaultMessage: '{previewTitle}',
defaultMessage: '{previewTitle} problem',
description: 'Title for the problem preview column',
},
previewAltText: {
@@ -61,8 +61,13 @@ export const messages = {
},
learnMoreButtonLabel: {
id: 'authoring.problemEditor.learnMoreButtonLabel.label',
defaultMessage: 'Learn More',
description: 'Label for Learn More button',
defaultMessage: 'Learn more',
description: 'Label for Learn more button',
},
learnMoreAdvancedButtonLabel: {
id: 'authoring.problemEditor.advanceProblem.learnMoreButtonLabel.label',
defaultMessage: 'Learn more about advanced problem types',
description: 'Label for Learn more about advanced problem types button',
},
};

View File

@@ -4,6 +4,7 @@ import {
} from '../../../../data/constants/problem';
import { StrictDict } from '../../../../utils';
import * as module from './hooks';
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
export const state = StrictDict({
selected: (val) => useState(val),
@@ -17,11 +18,16 @@ export const selectHooks = () => {
};
};
export const onSelect = (setProblemType, selected, updateField) => () => {
export const onSelect = ({ selected, updateField, setBlockTitle }) => () => {
if (Object.values(AdvanceProblemKeys).includes(selected)) {
updateField({ rawOLX: AdvanceProblems[selected].template });
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
setBlockTitle(AdvanceProblems[selected].title);
} else {
const newOLX = ProblemTypes[selected].template;
const { settings, ...newState } = getDataFromOlx({ rawOLX: newOLX, rawSettings: {} });
updateField({ ...newState });
setBlockTitle(ProblemTypes[selected].title);
}
setProblemType({ selected });
};
export const useArrowNav = (selected, setSelected) => {

View File

@@ -2,7 +2,8 @@
import React from 'react';
import { MockUseState } from '../../../../../testUtils';
import * as module from './hooks';
import { AdvanceProblems, ProblemTypeKeys } from '../../../../data/constants/problem';
import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '../../../../data/constants/problem';
import { OLXParser } from '../../data/OLXParser';
jest.mock('react', () => ({
...jest.requireActual('react'),
@@ -11,11 +12,11 @@ jest.mock('react', () => ({
}));
const state = new MockUseState(module);
const mockSetProblemType = jest.fn().mockName('setProblemType');
const mockUpdateField = jest.fn().mockName('updateField');
const mockSelected = 'vAl';
const mockAdvancedSelected = 'blankadvanced';
const mockSelected = 'multiplechoiceresponse';
const mockAdvancedSelected = 'circuitschematic';
const mockSetSelected = jest.fn().mockName('setSelected');
const mocksetBlockTitle = jest.fn().mockName('setBlockTitle');
let hook;
@@ -45,14 +46,26 @@ describe('SelectTypeModal hooks', () => {
describe('onSelect', () => {
test('updateField is called with selected templated if selected is an Advanced Problem', () => {
module.onSelect(mockSetProblemType, mockAdvancedSelected, mockUpdateField)();
module.onSelect({
selected: mockAdvancedSelected,
updateField: mockUpdateField,
setBlockTitle: mocksetBlockTitle,
})();
expect(mockUpdateField).toHaveBeenCalledWith({
problemType: ProblemTypeKeys.ADVANCED,
rawOLX: AdvanceProblems[mockAdvancedSelected].template,
});
expect(mocksetBlockTitle).toHaveBeenCalledWith(AdvanceProblems[mockAdvancedSelected].title);
});
test('setProblemType is called with selected', () => {
module.onSelect(mockSetProblemType, mockSelected, mockUpdateField)();
expect(mockSetProblemType).toHaveBeenCalledWith({ selected: mockSelected });
test('updateField is called with selected on visual propblems', () => {
module.onSelect({ selected: mockSelected, updateField: mockUpdateField, setBlockTitle: mocksetBlockTitle })();
const testOlXParser = new OLXParser(ProblemTypes[mockSelected].template);
const { settings, ...testState } = testOlXParser.getParsedOLXData();
expect(mockUpdateField).toHaveBeenCalledWith({
...testState,
rawOLX: ProblemTypes[mockSelected].template,
});
expect(mocksetBlockTitle).toHaveBeenCalledWith(ProblemTypes[mockSelected].title);
});
});

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from '@edx/paragon';
import { Row, Stack } from '@edx/paragon';
import ProblemTypeSelect from './content/ProblemTypeSelect';
import Preview from './content/Preview';
import AdvanceTypeSelect from './content/AdvanceTypeSelect';
@@ -17,16 +17,12 @@ export const SelectTypeModal = ({
return (
<SelectTypeWrapper onClose={onClose} selected={selected}>
<Row className="justify-content-center align-items-center m-4">
<Row className="justify-content-center">
{(!Object.values(AdvanceProblemKeys).includes(selected)) ? (
<>
<Col>
<ProblemTypeSelect selected={selected} setSelected={setSelected} />
</Col>
<Col>
<Preview problemType={selected} />
</Col>
</>
<Stack direction="horizontal" gap={4} className="flex-wrap">
<ProblemTypeSelect selected={selected} setSelected={setSelected} />
<Preview problemType={selected} />
</Stack>
) : <AdvanceTypeSelect selected={selected} setSelected={setSelected} />}
</Row>
</SelectTypeWrapper>

View File

@@ -280,7 +280,7 @@ export class OLXParser {
the parsed OLX.
*/
const tagMap = {
label: 'bold',
label: 'strong',
description: 'em',
};
@@ -331,10 +331,16 @@ export class OLXParser {
getProblemType() {
const problemKeys = Object.keys(this.problem);
const intersectedProblems = _.intersection(Object.values(ProblemTypeKeys), problemKeys);
if (intersectedProblems.length === 0) {
return null;
// a blank problem is a problem which contains only `<problem></problem>` as it's olx.
// blank problems are not given types, so that a type may be selected.
if (problemKeys.length === 1 && problemKeys[0] === '#text' && this.problem[problemKeys[0]] === '') {
return null;
}
// if we have no matching problem type, the problem is advanced.
return ProblemTypeKeys.ADVANCED;
}
// make sure compound problems are treated as advanced
if (intersectedProblems.length > 1) {
return ProblemTypeKeys.ADVANCED;
}
@@ -369,7 +375,10 @@ export class OLXParser {
answersObject = this.parseMultipleChoiceAnswers(ProblemTypeKeys.SINGLESELECT, 'choicegroup', 'choice');
break;
case ProblemTypeKeys.ADVANCED:
break;
return {
problemType,
settings: {},
};
default:
// if problem is unset, return null
return {};

View File

@@ -7,6 +7,8 @@ import {
textInputWithFeedbackAndHintsOLX,
mutlipleChoiceWithFeedbackAndHintsOLX,
textInputWithFeedbackAndHintsOLXWithMultipleAnswers,
advancedProblemOlX,
blankProblemOLX,
} from './mockData/olxTestData';
import { ProblemTypeKeys } from '../../../data/constants/problem';
@@ -36,6 +38,16 @@ describe('Check OLXParser problem type', () => {
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.TEXTINPUT);
});
test('Test Advanced Problem Type', () => {
const olxparser = new OLXParser(advancedProblemOlX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
});
test('Test Blank Problem Type', () => {
const olxparser = new OLXParser(blankProblemOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(null);
});
});
describe('Check OLXParser hints', () => {

View File

@@ -16,10 +16,7 @@ export const parseScoringSettings = (metadata) => {
let attempts = popuplateItem({}, 'max_attempts', 'number', metadata);
if (!_.isEmpty(attempts)) {
let unlimited = true;
if (attempts.number > 0) {
unlimited = false;
}
const unlimited = _.isNaN(attempts.number);
attempts = { ...attempts, unlimited };
scoring = { ...scoring, attempts };
}

View File

@@ -4,7 +4,7 @@ import {
dropdownWithFeedbackHints,
numericWithHints,
textInputWithHints,
sigleSelectWithHints,
singleSelectWithHints,
} from './mockData/problemTestData';
describe('Test Settings to State Parser', () => {
@@ -38,7 +38,7 @@ describe('Test Settings to State Parser', () => {
});
test('Test score settings missing', () => {
const settings = parseSettings(sigleSelectWithHints.metadata);
const settings = parseSettings(singleSelectWithHints.metadata);
expect(settings.scoring).toBeUndefined();
});

View File

@@ -82,11 +82,11 @@ export const checkboxesOLXWithFeedbackAndHintsOLX = {
},
],
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<choiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<checkboxgroup>
<choice correct="true">
@@ -160,11 +160,11 @@ export const dropdownOLXWithFeedbackAndHintsOLX = {
},
],
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<optionresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<optioninput>
<option correct="false">
@@ -233,11 +233,11 @@ export const mutlipleChoiceWithFeedbackAndHintsOLX = {
},
],
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<multiplechoiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<choicegroup>
<choice correct="false">
@@ -299,10 +299,10 @@ export const numericInputWithFeedbackAndHintsOLX = {
},
],
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<numericalresponse answer="100">
<responseparam type="tolerance" default="5"></responseparam>
@@ -373,11 +373,11 @@ export const textInputWithFeedbackAndHintsOLX = {
},
},
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<stringresponse answer="the correct answer" type="ci">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
<additional_answer answer="optional acceptable variant of the correct answer"></additional_answer>
@@ -452,11 +452,11 @@ export const textInputWithFeedbackAndHintsOLXWithMultipleAnswers = {
},
},
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<stringresponse answer="the correct answer" type="ci">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
<additional_answer answer="300">
@@ -531,10 +531,10 @@ export const numericInputWithFeedbackAndHintsOLXException = {
},
],
},
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><bold>Add the question text, or prompt, here. This text is required.</bold><em>You can add an optional tip or note related to the prompt like this.</em>',
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><strong>Add the question text, or prompt, here. This text is required.</strong><em>You can add an optional tip or note related to the prompt like this.</em>',
buildOLX: `<problem>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<bold>Add the question text, or prompt, here. This text is required.</bold>
<strong>Add the question text, or prompt, here. This text is required.</strong>
<em>You can add an optional tip or note related to the prompt like this.</em>
<numericalresponse answer="300">
<additional_answer answer="100">
@@ -553,3 +553,17 @@ export const numericInputWithFeedbackAndHintsOLXException = {
</problem>
`,
};
export const advancedProblemOlX = {
rawOLX: `<problem>
<formularesponse type="ci" samples="R_1,R_2,R_3@1,2,3:3,4,5#10" answer="R_1*R_2/R_3">
<p>You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required. Example: Write an expression for the product of R_1, R_2, and the inverse of R_3.</label>
<description>You can add an optional tip or note related to the prompt like this. Example: To test this example, the correct answer is R_1*R_2/R_3</description>
<responseparam type="tolerance" default="0.00001"/>
<formulaequationinput size="40"/>
</formularesponse>
</problem>`,
};
export const blankProblemOLX = {
rawOLX: '<problem></problem>',
};

View File

@@ -217,7 +217,7 @@ export const numericWithHints = {
scoring: {
weight: 2.5,
attempts: {
unlimited: true,
unlimited: false,
number: 0,
},
},
@@ -288,7 +288,7 @@ export const textInputWithHints = {
scoring: {
weight: 2.5,
attempts: {
unlimited: true,
unlimited: false,
number: 0,
},
},
@@ -315,7 +315,7 @@ not=optional incorrect answer such as a frequent misconception {{You can specify
},
};
export const sigleSelectWithHints = {
export const singleSelectWithHints = {
state: {
rawOLX: '<problem>\n<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>\n\n<label>Add the question text, or prompt, here. This text is required.</label>\n<description>You can add an optional tip or note related to the prompt like this.</description>\n<multiplechoiceresponse>\n <choicegroup type="MultipleChoice">\n <choice correct="true">a correct answer <choicehint>selected: You can specify optional feedback that appears after the learner selects and submits this answer. }, { unselected: You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint></choice>\n <choice correct="false">an incorrect answer</choice>\n <choice correct="false">an incorrect answer <choicehint>selected: You can specify optional feedback for none, all, or a subset of the answers. }, { unselected: You can specify optional feedback for selected answers, cleared answers, or both.</choicehint></choice>\n <choice correct="false">an incorrect answer again</choice>\n </choicegroup>\n</multiplechoiceresponse>\n<choiceresponse>\n <checkboxgroup>\n <compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>\n <compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>\n </checkboxgroup>\n</choiceresponse>\n\n\n<demandhint>\n <hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>\n <hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>\n</demandhint>\n</problem>',
problemType: 'SINGLESELECT',
@@ -362,7 +362,7 @@ export const sigleSelectWithHints = {
weight: 0,
attempts: {
unlimited: true,
number: 0,
number: null,
},
},
timeBetween: 0,

View File

@@ -1,7 +1,7 @@
import {
useRef, useCallback, useState, useEffect,
} from 'react';
import { ProblemTypeKeys } from '../../data/constants/problem';
import tinyMCEStyles from '../../data/constants/tinyMCEStyles';
import { StrictDict } from '../../utils';
import * as module from './hooks';
@@ -19,6 +19,13 @@ export const problemEditorConfig = ({
setEditorRef(editor);
},
initialValue: question || '',
init: {
skin: false,
content_css: false,
content_style: tinyMCEStyles,
menubar: false,
branding: false,
},
onFocusOut: () => {
const content = editorRef.current.getContent();
updateQuestion(content);
@@ -34,8 +41,3 @@ export const prepareEditorRef = () => {
useEffect(() => setRefReady(true), [setRefReady]);
return { editorRef, refReady, setEditorRef };
};
export const initializeAnswerContainer = (problemType) => {
const hasSingleAnswer = problemType === ProblemTypeKeys.DROPDOWN || problemType === ProblemTypeKeys.SINGLESELECT;
return { hasSingleAnswer };
};

View File

@@ -17,8 +17,6 @@ export const ProblemEditor = ({
blockValue,
initializeProblemEditor,
}) => {
React.useEffect(() => initializeProblemEditor(blockValue), [blockValue]);
// TODO: INTL MSG, Add LOAD FAILED ERROR using BLOCKFAILED
if (!blockFinished || !studioViewFinished) {
return (
<div className="text-center p-6">
@@ -31,6 +29,8 @@ export const ProblemEditor = ({
);
}
// once data is loaded, init store
React.useEffect(() => initializeProblemEditor(blockValue), [blockValue]);
// TODO: INTL MSG, Add LOAD FAILED ERROR using BLOCKFAILED
if (problemType === null) {
return (<SelectTypeModal onClose={onClose} />);

View File

@@ -236,6 +236,13 @@ exports[`TextEditor snapshots loaded, raw editor 1`] = `
/>
</Toast>
<RawEditor
content={
Object {
"data": Object {
"data": "eDiTablE Text",
},
}
}
editorRef={
Object {
"current": Object {
@@ -243,13 +250,7 @@ exports[`TextEditor snapshots loaded, raw editor 1`] = `
},
}
}
text={
Object {
"data": Object {
"data": "eDiTablE Text",
},
}
}
lang="html"
/>
</div>
</EditorContainer>

View File

@@ -1,109 +0,0 @@
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import {
Button,
} from '@edx/paragon';
import { basicSetup } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { html } from '@codemirror/lang-html';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import alphanumericMap from './constants';
import * as module from './index';
import messages from './messages';
import './index.scss';
export const hooks = {
state: {
showBtnEscapeHTML: (val) => React.useState(val),
},
prepareShowBtnEscapeHTML: () => {
const [visibility, setVisibility] = hooks.state.showBtnEscapeHTML(true);
const hide = () => setVisibility(false);
return { showBtnEscapeHTML: visibility, hideBtn: hide };
},
createCodeMirrorDomNode: ({ ref, initialText, upstreamRef }) => {
useEffect(() => {
const cleanText = hooks.cleanHTML({ initialText });
const state = EditorState.create({
doc: cleanText,
extensions: [basicSetup, html(), EditorView.lineWrapping],
});
const view = new EditorView({ state, parent: ref.current });
// eslint-disable-next-line no-param-reassign
upstreamRef.current = view;
view.focus();
return () => {
// called on cleanup
view.destroy();
};
}, []);
},
cleanHTML: ({ initialText }) => {
const translateRegex = new RegExp(`&(${Object.keys(alphanumericMap).join('|')});`, 'g');
const translator = ($0, $1) => alphanumericMap[$1];
return initialText.replace(translateRegex, translator);
},
escapeHTMLSpecialChars: ({ ref, hideBtn }) => {
const text = ref.current.state.doc.toString(); let
pos = 0;
const changes = [];
Object.keys(alphanumericMap).forEach(
(escapedKeyword) => {
// eslint-disable-next-line no-cond-assign
for (let next; (next = text.indexOf(alphanumericMap[escapedKeyword], pos)) > -1;) {
changes.push({ from: next, to: next + 1, insert: `&${escapedKeyword};` });
pos = next + 1;
}
},
);
ref.current.dispatch({ changes });
hideBtn();
},
};
export const CodeEditor = ({
innerRef,
value,
// injected
intl,
}) => {
const DOMref = useRef();
const btnRef = useRef();
module.hooks.createCodeMirrorDomNode({ ref: DOMref, initialText: value, upstreamRef: innerRef });
const { showBtnEscapeHTML, hideBtn } = module.hooks.prepareShowBtnEscapeHTML();
return (
<div>
<div id="CodeMirror" ref={DOMref} />
{showBtnEscapeHTML && (
<Button
variant="tertiary"
aria-label={intl.formatMessage(messages.escapeHTMLButtonLabel)}
ref={btnRef}
onClick={() => module.hooks.escapeHTMLSpecialChars({ ref: innerRef, hideBtn })}
>
<FormattedMessage {...messages.escapeHTMLButtonLabel} />
</Button>
)}
</div>
);
};
CodeEditor.propTypes = {
innerRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.any }),
]).isRequired,
value: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};
export default injectIntl(CodeEditor);

View File

@@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
import CodeEditor from '../CodeEditor';
export const RawEditor = ({
editorRef,
text,
}) => (
<div style={{ padding: '10px 30px', height: '600px' }}>
<Alert variant="danger">
You are using the raw HTML editor.
</Alert>
{ text && text.data.data ? (
<CodeEditor
innerRef={editorRef}
value={text.data.data}
/>
) : null}
</div>
);
RawEditor.defaultProps = {
editorRef: null,
text: null,
};
RawEditor.propTypes = {
editorRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.any }),
]),
text: PropTypes.shape({
data: PropTypes.shape({ data: PropTypes.string }),
}),
};
export default RawEditor;

View File

@@ -12,7 +12,7 @@ import messages from './messages';
import hooks from './hooks';
import BaseModal from '../BaseModal';
import CodeEditor from '../CodeEditor';
import CodeEditor from '../../../../sharedComponents/CodeEditor';
export const SourceCodeModal = ({
isOpen,

View File

@@ -32,7 +32,7 @@ import { RequestKeys } from '../../data/constants/requests';
import EditorContainer from '../EditorContainer';
import ImageUploadModal from './components/ImageUploadModal';
import SourceCodeModal from './components/SourceCodeModal';
import RawEditor from './components/RawEditor';
import RawEditor from '../../sharedComponents/RawEditor';
import * as hooks from './hooks';
import messages from './messages';
@@ -64,7 +64,7 @@ export const TextEditor = ({
return (
<RawEditor
editorRef={editorRef}
text={blockValue}
content={blockValue}
/>
);
}

View File

@@ -1,87 +0,0 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { Col, Form } from '@edx/paragon';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { keyStore } from '../../../../../utils';
import CollapsibleFormWidget from './CollapsibleFormWidget';
import hooks from './hooks';
import { durationFromValue } from './duration';
import messages from './messages';
/**
* Collapsible Form widget controlling video start and end times
* Also displays the total run time of the video.
*/
export const DurationWidget = ({
// injected
intl,
}) => {
const dispatch = useDispatch();
const { duration } = hooks.widgetValues({
dispatch,
fields: { [hooks.selectorKeys.duration]: hooks.durationWidget },
});
const timeKeys = keyStore(duration.formValue);
const getTotalLabel = (startTime, stopTime, subtitle) => {
if (!stopTime) {
if (!startTime) {
return intl.formatMessage(messages.fullVideoLength);
}
if (subtitle) {
return intl.formatMessage(messages.startsAt, { startTime: durationFromValue(startTime) });
}
return null;
}
const total = stopTime - (startTime || 0);
return intl.formatMessage(messages.total, { total: durationFromValue(total) });
};
return (
<CollapsibleFormWidget
fontSize="x-small"
title={intl.formatMessage(messages.durationTitle)}
subtitle={getTotalLabel(duration.formValue.startTime, duration.formValue.stopTime, true)}
>
<FormattedMessage {...messages.durationDescription} />
<Form.Row className="mt-4.5">
<Form.Group as={Col}>
<Form.Control
floatingLabel={intl.formatMessage(messages.startTimeLabel)}
onBlur={duration.onBlur(timeKeys.startTime)}
onChange={duration.onChange(timeKeys.startTime)}
onKeyDown={duration.onKeyDown(timeKeys.startTime)}
value={duration.local.startTime}
/>
<Form.Control.Feedback>
<FormattedMessage {...messages.durationHint} />
</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col}>
<Form.Control
floatingLabel={intl.formatMessage(messages.stopTimeLabel)}
onBlur={duration.onBlur(timeKeys.stopTime)}
onChange={duration.onChange(timeKeys.stopTime)}
onKeyDown={duration.onKeyDown(timeKeys.stopTime)}
value={duration.local.stopTime}
/>
<Form.Control.Feedback>
<FormattedMessage {...messages.durationHint} />
</Form.Control.Feedback>
</Form.Group>
</Form.Row>
<div className="mt-4">
{getTotalLabel(duration.formValue.startTime, duration.formValue.stopTime)}
</div>
</CollapsibleFormWidget>
);
};
DurationWidget.propTypes = {
// injected
intl: intlShape.isRequired,
};
export default injectIntl(DurationWidget);

View File

@@ -1,22 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { formatMessage } from '../../../../../../testUtils';
import { DurationWidget } from './DurationWidget';
describe('DurationWidget', () => {
const props = {
isError: false,
subtitle: 'SuBTItle',
title: 'tiTLE',
// inject
intl: { formatMessage },
};
describe('render', () => {
test('snapshots: renders as expected with default props', () => {
expect(
shallow(<DurationWidget {...props} />),
).toMatchSnapshot();
});
});
});

View File

@@ -3,7 +3,7 @@
exports[`DurationWidget render snapshots: renders as expected with default props 1`] = `
<injectIntl(ShimmedIntlComponent)
fontSize="x-small"
subtitle="Full video length"
subtitle="Total: 00:00:00"
title="Duration"
>
<FormattedMessage
@@ -14,7 +14,9 @@ exports[`DurationWidget render snapshots: renders as expected with default props
<Form.Row
className="mt-4.5"
>
<Form.Group>
<Form.Group
as="Col"
>
<Form.Control
floatingLabel="Start time"
onBlur={[Function]}
@@ -30,7 +32,9 @@ exports[`DurationWidget render snapshots: renders as expected with default props
/>
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Group
as="Col"
>
<Form.Control
floatingLabel="Stop time"
onBlur={[Function]}
@@ -50,7 +54,7 @@ exports[`DurationWidget render snapshots: renders as expected with default props
<div
className="mt-4"
>
Full video length
Total: 00:00:00
</div>
</injectIntl(ShimmedIntlComponent)>
`;

Some files were not shown because too many files have changed in this diff Show More