Compare commits

..

4 Commits

Author SHA1 Message Date
Jacobo Dominguez
db0a56566f docs: add comprehensive readme documentation for plugin slots (#1770) 2025-08-06 16:41:36 +05:30
Brian Smith
b78e78313b fix(docs): correct ProgressCertificateStatusSlot README title (#1689) 2025-07-21 13:11:29 -04:00
Brian Smith
29ec85ddbe fix(docs): correct CourseRecommendationsSlot README title (#1688) 2025-07-21 13:11:29 -04:00
Maxim Beder
241e188465 feat: update certificate icons
Old certificates icons contained edX trademark logos, which were not
suitable for the open source repos. Replaced with icons that contain
Open edX logos.
2025-05-05 20:43:01 +05:30
131 changed files with 4720 additions and 3481 deletions

5
.env
View File

@@ -12,12 +12,10 @@ CREDIT_HELP_LINK_URL=''
CSRF_TOKEN_API_PATH='' CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL='' DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL='' DISCUSSIONS_MFE_BASE_URL=''
DISCOUNT_CODE_INFO_URL=''
ECOMMERCE_BASE_URL='' ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true' ENABLE_JUMPNAV='true'
ENABLE_NOTICES='' ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='' ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
ENTERPRISE_LEARNER_PORTAL_URL=''
EXAMS_BASE_URL='' EXAMS_BASE_URL=''
FAVICON_URL='' FAVICON_URL=''
IGNORED_ERROR_REGEX='' IGNORED_ERROR_REGEX=''
@@ -51,6 +49,3 @@ TWITTER_URL=''
USER_INFO_COOKIE_NAME='' USER_INFO_COOKIE_NAME=''
OPTIMIZELY_FULL_STACK_SDK_KEY='' OPTIMIZELY_FULL_STACK_SDK_KEY=''
SHOW_UNGRADED_ASSIGNMENT_PROGRESS='' SHOW_UNGRADED_ASSIGNMENT_PROGRESS=''
# Fallback in local style files
PARAGON_THEME_URLS={}
FEATURE_ENABLE_CHAT_V2_ENDPOINT=''

View File

@@ -12,12 +12,10 @@ CREDIT_HELP_LINK_URL='https://help.edx.org/edxlearner/s/article/Can-I-receive-co
CSRF_TOKEN_API_PATH='/csrf/api/v1/token' CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381' DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002' DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
DISCOUNT_CODE_INFO_URL=''
ECOMMERCE_BASE_URL='http://localhost:18130' ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true' ENABLE_JUMPNAV='true'
ENABLE_NOTICES='' ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734' ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
EXAMS_BASE_URL='' EXAMS_BASE_URL=''
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
IGNORED_ERROR_REGEX='' IGNORED_ERROR_REGEX=''
@@ -53,6 +51,3 @@ CHAT_RESPONSE_URL='http://localhost:18000/api/learning_assistant/v1/course_id'
PRIVACY_POLICY_URL='http://localhost:18000/privacy' PRIVACY_POLICY_URL='http://localhost:18000/privacy'
OPTIMIZELY_FULL_STACK_SDK_KEY='' OPTIMIZELY_FULL_STACK_SDK_KEY=''
SHOW_UNGRADED_ASSIGNMENT_PROGRESS='' SHOW_UNGRADED_ASSIGNMENT_PROGRESS=''
# Fallback in local style files
PARAGON_THEME_URLS={}
FEATURE_ENABLE_CHAT_V2_ENDPOINT='false'

View File

@@ -12,12 +12,10 @@ CREDIT_HELP_LINK_URL='https://help.edx.org/edxlearner/s/article/Can-I-receive-co
CSRF_TOKEN_API_PATH='/csrf/api/v1/token' CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381' DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002' DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
DISCOUNT_CODE_INFO_URL=''
ECOMMERCE_BASE_URL='http://localhost:18130' ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true' ENABLE_JUMPNAV='true'
ENABLE_NOTICES='' ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734' ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
EXAMS_BASE_URL='http://localhost:18740' EXAMS_BASE_URL='http://localhost:18740'
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
IGNORED_ERROR_REGEX='' IGNORED_ERROR_REGEX=''
@@ -50,5 +48,3 @@ TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info' USER_INFO_COOKIE_NAME='edx-user-info'
PRIVACY_POLICY_URL='http://localhost:18000/privacy' PRIVACY_POLICY_URL='http://localhost:18000/privacy'
SHOW_UNGRADED_ASSIGNMENT_PROGRESS='' SHOW_UNGRADED_ASSIGNMENT_PROGRESS=''
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:Enterprise'
FEATURE_ENABLE_CHAT_V2_ENDPOINT='false'

View File

@@ -1,18 +0,0 @@
# Run the workflow that adds new tickets that are labelled "release testing"
# to the org-wide BTR project board
name: Add release testing issues to the BTR project board
on:
issues:
types: [labeled]
# This workflow is triggered when an issue is labeled with 'release testing'.
# It adds the issue to the BTR project and applies the 'needs triage' label
# if it doesn't already have it.
jobs:
handle-release-testing:
uses: openedx/.github/.github/workflows/add-issue-to-btr-project.yml@master
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}

View File

@@ -10,8 +10,8 @@ jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: actions/setup-node@v6 - uses: actions/setup-node@v4
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: make validate.ci - run: make validate.ci
@@ -24,11 +24,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: tests needs: tests
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Download code coverage results - name: Download code coverage results
uses: actions/download-artifact@v5 uses: actions/download-artifact@v4
with: with:
pattern: code-coverage-report name: code-coverage-report
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:

2
.nvmrc
View File

@@ -1 +1 @@
24 20

2427
package-lock.json generated
View File

@@ -12,18 +12,19 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "1.5.0", "@edx/browserslist-config": "1.5.0",
"@edx/frontend-component-footer": "^14.6.0", "@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^8.0.0", "@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-lib-learning-assistant": "^2.23.1", "@edx/frontend-lib-learning-assistant": "^2.20.0",
"@edx/frontend-lib-special-exams": "^4.0.0", "@edx/frontend-lib-special-exams": "^3.5.0",
"@edx/frontend-platform": "^8.4.0", "@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.7.0", "@edx/openedx-atlas": "^0.6.0",
"@edx/react-unit-test-utils": "^4.0.0",
"@fortawesome/free-brands-svg-icons": "5.15.4", "@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.4", "@fortawesome/react-fontawesome": "^0.1.4",
"@openedx/frontend-build": "^14.6.2", "@openedx/frontend-build": "^14.5.0",
"@openedx/frontend-plugin-framework": "^1.7.0", "@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/paragon": "^23.4.5", "@openedx/paragon": "^22.16.0",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@reduxjs/toolkit": "1.9.7", "@reduxjs/toolkit": "1.9.7",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -51,6 +52,7 @@
"truncate-html": "1.0.4" "truncate-html": "1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@edx/reactifex": "2.2.0",
"@pact-foundation/pact": "^13.0.0", "@pact-foundation/pact": "^13.0.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
@@ -68,7 +70,6 @@
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz",
"integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -123,14 +124,14 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.27.1", "@babel/helper-validator-identifier": "^7.25.9",
"js-tokens": "^4.0.0", "js-tokens": "^4.0.0",
"picocolors": "^1.1.1" "picocolors": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -150,7 +151,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2", "@babel/code-frame": "^7.26.2",
@@ -413,9 +413,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1940,158 +1940,6 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@bundled-es-modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==",
"license": "ISC",
"dependencies": {
"deepmerge": "^4.3.1"
}
},
"node_modules/@bundled-es-modules/glob": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/glob/-/glob-10.4.2.tgz",
"integrity": "sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"buffer": "^6.0.3",
"events": "^3.3.0",
"glob": "^10.4.2",
"patch-package": "^8.0.0",
"path": "^0.12.7",
"stream": "^0.0.3",
"string_decoder": "^1.3.0",
"url": "^0.11.3"
}
},
"node_modules/@bundled-es-modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@bundled-es-modules/glob/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@bundled-es-modules/glob/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@bundled-es-modules/memfs": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.17.0.tgz",
"integrity": "sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==",
"license": "Apache-2.0",
"dependencies": {
"assert": "^2.1.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"memfs": "^4.17.0",
"path": "^0.12.7",
"stream": "^0.0.3",
"util": "^0.12.5"
}
},
"node_modules/@bundled-es-modules/memfs/node_modules/memfs": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.1.tgz",
"integrity": "sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==",
"license": "Apache-2.0",
"dependencies": {
"@jsonjoy.com/json-pack": "^1.0.3",
"@jsonjoy.com/util": "^1.3.0",
"tree-dump": "^1.0.1",
"tslib": "^2.0.0"
},
"engines": {
"node": ">= 4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
}
},
"node_modules/@bundled-es-modules/postcss-calc-ast-parser": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz",
"integrity": "sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==",
"license": "ISC",
"dependencies": {
"postcss-calc-ast-parser": "^0.1.4"
}
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"license": "Apache-2.0"
},
"node_modules/@cospired/i18n-iso-languages": { "node_modules/@cospired/i18n-iso-languages": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-4.2.0.tgz",
@@ -2139,7 +1987,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "^14 || ^16 || >=18" "node": "^14 || ^16 || >=18"
}, },
@@ -2162,7 +2009,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "^14 || ^16 || >=18" "node": "^14 || ^16 || >=18"
} }
@@ -2210,8 +2056,7 @@
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.5.0.tgz",
"integrity": "sha512-d2ggwi5j4DOBJOwhWZxBWQSDR0DhT4ke/1PbzRauICdFkuOyax+PsFjK8GUh443K2OaQpy9PGfiCzZ1Yg37AUA==", "integrity": "sha512-d2ggwi5j4DOBJOwhWZxBWQSDR0DhT4ke/1PbzRauICdFkuOyax+PsFjK8GUh443K2OaQpy9PGfiCzZ1Yg37AUA==",
"license": "AGPL-3.0", "license": "AGPL-3.0"
"peer": true
}, },
"node_modules/@edx/eslint-config": { "node_modules/@edx/eslint-config": {
"version": "4.3.0", "version": "4.3.0",
@@ -2231,16 +2076,16 @@
} }
}, },
"node_modules/@edx/frontend-component-footer": { "node_modules/@edx/frontend-component-footer": {
"version": "14.9.2", "version": "14.6.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-14.9.2.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-14.6.0.tgz",
"integrity": "sha512-koYtfZK9flTO3ExAmaP0HDlxbV9XX8hbRE/8WNtMJh+X1B8xppT3Ft8vhGDsw6dEBo9ojndmU9805G/a8/8o3g==", "integrity": "sha512-cgRhom6W/WErQ9yvLmfgB6ANBs+rBDLOH73NcvJIhfwWgAg67q+MLUscIbcX9N/9Yykk+kb7Ytr3CDefiKS7HA==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2", "@fortawesome/free-brands-svg-icons": "6.7.2",
"@fortawesome/free-regular-svg-icons": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.6", "@fortawesome/react-fontawesome": "0.2.2",
"@openedx/frontend-plugin-framework": "^1.7.0", "@openedx/frontend-plugin-framework": "^1.7.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
@@ -2292,31 +2137,34 @@
} }
}, },
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/react-fontawesome": { "node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.6", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.6.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==", "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", "@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": ">=16.3"
} }
}, },
"node_modules/@edx/frontend-component-header": { "node_modules/@edx/frontend-component-header": {
"version": "8.0.0", "version": "6.4.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-8.0.0.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-6.4.0.tgz",
"integrity": "sha512-AD6ImSI2APSKBOA1a3P4/Uy6YlpEYlSePpKbNNr2YjmY5nB5yWcNjE38sBBIBHLbC2/b+AwgGrv+m1bxej5xTQ==", "integrity": "sha512-RNV3XRXhhN9QlhAoP26CjzoRIPlLSYDp3PZCnK6g6kIHgxC9dCpu2PTZdxV2AVChqVuxtZK5zLbk9yeAtf4U/A==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.7.2", "@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/frontend-plugin-framework": "^1.7.0", "@openedx/frontend-plugin-framework": "^1.7.0",
"axios-mock-adapter": "1.22.0",
"babel-polyfill": "6.26.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"jest-environment-jsdom": "^29.7.0",
"react-responsive": "8.2.0", "react-responsive": "8.2.0",
"react-transition-group": "4.4.5" "react-transition-group": "4.4.5"
}, },
@@ -2329,59 +2177,93 @@
"react-router-dom": "^6.14.2" "react-router-dom": "^6.14.2"
} }
}, },
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-brands-svg-icons": { "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.7.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
"integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
"license": "(CC-BY-4.0 AND MIT)", "license": "(CC-BY-4.0 AND MIT)",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2" "@fortawesome/fontawesome-common-types": "6.6.0"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-regular-svg-icons": { "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.7.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz",
"integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==",
"license": "(CC-BY-4.0 AND MIT)", "license": "(CC-BY-4.0 AND MIT)",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2" "@fortawesome/fontawesome-common-types": "6.6.0"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-solid-svg-icons": { "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
"license": "(CC-BY-4.0 AND MIT)", "license": "(CC-BY-4.0 AND MIT)",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2" "@fortawesome/fontawesome-common-types": "6.6.0"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/react-fontawesome": { "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.6", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.6.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==", "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", "@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": ">=16.3"
}
},
"node_modules/@edx/frontend-component-header/node_modules/axios-mock-adapter": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz",
"integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"is-buffer": "^2.0.5"
},
"peerDependencies": {
"axios": ">= 0.17.0"
} }
}, },
"node_modules/@edx/frontend-lib-learning-assistant": { "node_modules/@edx/frontend-lib-learning-assistant": {
"version": "2.23.1", "version": "2.21.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.23.1.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.21.0.tgz",
"integrity": "sha512-0rDHlE3tlADWOcqKaVIKkMK2YGonbRaYJfmBSgH+Sn6+BFg2e541fn7NC9e5rIaiV1BnMREF7dxyRa/IEYLZLA==", "integrity": "sha512-CUzPCQaBgXi6E1kvY0nyBSVFu8RUGpwKH4V0p8ZuysyHyRHpA+339b+gEi9FvVBMP/X4IxZHsZhi7nphlr43Iw==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
@@ -2405,9 +2287,9 @@
} }
}, },
"node_modules/@edx/frontend-lib-special-exams": { "node_modules/@edx/frontend-lib-special-exams": {
"version": "4.0.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-3.5.0.tgz",
"integrity": "sha512-mJdrxebdKO9NxDFkQZ1vyWVUvWCk393pIVHJyz9vH42Kvn08LC5db8/gYk37srCfsA4Dl78pMLa408CoT14JMA==", "integrity": "sha512-lRKD3K+XAuoKAaxbZxb7QLTWkSlV9yIy08XflYoHh/weClesVTETU3+NtJ5YRsC/kYHZrzSYIpMZnBnkKTGTww==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/fontawesome-svg-core": "1.2.34",
@@ -2447,7 +2329,6 @@
"integrity": "sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==", "integrity": "sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.34" "@fortawesome/fontawesome-common-types": "^0.2.34"
}, },
@@ -2505,17 +2386,16 @@
} }
}, },
"node_modules/@edx/frontend-platform": { "node_modules/@edx/frontend-platform": {
"version": "8.4.0", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.3.4.tgz",
"integrity": "sha512-toWMU7qVx56f5bLk6/Sl5WWqlKtGp602qDs22JYp5r2VBp5F/nzcrpXXWC925/kH0TP5hI2OMolmLq6n2N8a4Q==", "integrity": "sha512-V3XtTo3KP8QSmId+Vvi4+qzpOVkxvTMNA6jH/i3Bfz+/jHjHBRnmp/Cc2pjTxiTgGNoKX4D1twiZkOBO+kWw1Q==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"peer": true,
"dependencies": { "dependencies": {
"@cospired/i18n-iso-languages": "4.2.0", "@cospired/i18n-iso-languages": "4.2.0",
"@formatjs/intl-pluralrules": "4.3.3", "@formatjs/intl-pluralrules": "4.3.3",
"@formatjs/intl-relativetimeformat": "10.0.1", "@formatjs/intl-relativetimeformat": "10.0.1",
"axios": "1.9.0", "axios": "1.8.4",
"axios-cache-interceptor": "1.8.0", "axios-cache-interceptor": "1.6.2",
"form-urlencoded": "4.1.4", "form-urlencoded": "4.1.4",
"glob": "7.2.3", "glob": "7.2.3",
"history": "4.10.1", "history": "4.10.1",
@@ -2544,12 +2424,6 @@
"react-redux": "^7.1.1 || ^8.1.1", "react-redux": "^7.1.1 || ^8.1.1",
"react-router-dom": "^6.0.0", "react-router-dom": "^6.0.0",
"redux": "^4.0.4" "redux": "^4.0.4"
},
"peerDependenciesMeta": {
"@openedx/frontend-build": {
"optional": true,
"reason": "This package is only a peer dependency to ensure using a minimum compatible version that provides env.config and PARAGON_THEME support. It is not needed at runtime, and may be omitted with `--omit=optional`."
}
} }
}, },
"node_modules/@edx/new-relic-source-map-webpack-plugin": { "node_modules/@edx/new-relic-source-map-webpack-plugin": {
@@ -2562,14 +2436,62 @@
} }
}, },
"node_modules/@edx/openedx-atlas": { "node_modules/@edx/openedx-atlas": {
"version": "0.7.0", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.6.2.tgz",
"integrity": "sha512-jqv0IV1pHsSn9+RO8Rdsr8jm3SOd84CCzzmo2QC9yvh1MK1+p4YDURQLpmmgKJ0JzE5Cb6ImhnNL/ogpJ2wetQ==", "integrity": "sha512-28Q8vzJDMS4wUxdkbIUBQpzWJ3HTdMaGlaEhFjrVGfuZkh++1AG6Tn/7FMD88cegalYAkphu530VQCHEkMZQhw==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"bin": { "bin": {
"atlas": "atlas" "atlas": "atlas"
} }
}, },
"node_modules/@edx/react-unit-test-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@edx/react-unit-test-utils/-/react-unit-test-utils-4.0.0.tgz",
"integrity": "sha512-QlVYhYD9L2bzx1eAtf8BbCJr00ek9rrHrG+/pW2bVSt+t0uvKHQpX1CNdMrDePv18DsMeC7IOB00t8ZIn4mi7w==",
"license": "AGPL-3.0",
"dependencies": {
"@edx/browserslist-config": "^1.1.1",
"@reduxjs/toolkit": "^1.5.1",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"classnames": "^2.2.6",
"core-js": "3.6.5",
"lodash": "^4.17.21",
"react-dev-utils": "^12.0.1",
"react-test-renderer": "^18.3.1"
},
"peerDependencies": {
"@edx/frontend-platform": "^8.3.1",
"@openedx/frontend-build": "^14.3.0",
"@openedx/paragon": "^22.0.0 || ^23.0.0",
"react": "^18.0.0"
}
},
"node_modules/@edx/reactifex": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@edx/reactifex/-/reactifex-2.2.0.tgz",
"integrity": "sha512-vyGDtx3BwCr6Gjbm4y6gJ8Bzc2TOSNBlBa2hMerz59HoXaot14MihxxiDU+JDNybGLLcKDBiK511bOi/77i1lw==",
"dev": true,
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"yargs": "^17.1.1"
},
"bin": {
"edx_reactifex": "main.js"
}
},
"node_modules/@edx/reactifex/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/@edx/typescript-config": { "node_modules/@edx/typescript-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@edx/typescript-config/-/typescript-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@edx/typescript-config/-/typescript-config-1.1.0.tgz",
@@ -2591,9 +2513,9 @@
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.5.0", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -3081,7 +3003,6 @@
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2" "@fortawesome/fontawesome-common-types": "6.7.2"
}, },
@@ -3233,520 +3154,6 @@
"deprecated": "Use @eslint/object-schema instead", "deprecated": "Use @eslint/object-schema instead",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
"integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.0"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
"integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.0"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
"integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
"integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
"integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
"integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
"integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
"integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
"integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
"integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
"integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
"integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.0"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
"integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.0"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
"integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.0"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
"integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.0"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
"integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.0"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
"integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
"integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.0"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
"integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.4.4"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
"integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
"integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
"integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@istanbuljs/load-nyc-config": { "node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -3954,7 +3361,6 @@
"resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
"integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@bcoe/v8-coverage": "^0.2.3", "@bcoe/v8-coverage": "^0.2.3",
"@jest/console": "^29.7.0", "@jest/console": "^29.7.0",
@@ -4054,7 +3460,6 @@
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
"integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.11.6", "@babel/core": "^7.11.6",
"@jest/types": "^29.6.3", "@jest/types": "^29.6.3",
@@ -4081,7 +3486,6 @@
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jest/schemas": "^29.6.3", "@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-lib-coverage": "^2.0.0",
@@ -4152,60 +3556,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@jsonjoy.com/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@jsonjoy.com/json-pack": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz",
"integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==",
"license": "Apache-2.0",
"dependencies": {
"@jsonjoy.com/base64": "^1.1.1",
"@jsonjoy.com/util": "^1.1.2",
"hyperdyperid": "^1.2.0",
"thingies": "^1.20.0"
},
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@jsonjoy.com/util": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz",
"integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@leichtgewicht/ip-codec": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@@ -4358,11 +3708,10 @@
} }
}, },
"node_modules/@openedx/frontend-build": { "node_modules/@openedx/frontend-build": {
"version": "14.6.2", "version": "14.5.0",
"resolved": "https://registry.npmjs.org/@openedx/frontend-build/-/frontend-build-14.6.2.tgz", "resolved": "https://registry.npmjs.org/@openedx/frontend-build/-/frontend-build-14.5.0.tgz",
"integrity": "sha512-Iu4/GPq90Xr/MSWnonn2qX8VDhI89HN7KOYBZ0/sxmAQgvXXNc7OYNC7kumvzbYzKueJQTyZoUYS7UjKB/n1WA==", "integrity": "sha512-HY0PdXvXBxrvJHj8HsRA+VNCHDePENFhqOIvbSz9Ke7HDwHpfDjg2CeKk41aU/8iTyj3eESfPwKQr5fTE0A3Ww==",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"peer": true,
"dependencies": { "dependencies": {
"@babel/cli": "7.24.8", "@babel/cli": "7.24.8",
"@babel/core": "7.24.9", "@babel/core": "7.24.9",
@@ -4407,7 +3756,7 @@
"file-loader": "6.2.0", "file-loader": "6.2.0",
"html-webpack-plugin": "5.6.3", "html-webpack-plugin": "5.6.3",
"identity-obj-proxy": "3.0.0", "identity-obj-proxy": "3.0.0",
"image-minimizer-webpack-plugin": "4.1.4", "image-minimizer-webpack-plugin": "3.8.3",
"jest": "29.7.0", "jest": "29.7.0",
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "29.7.0",
"mini-css-extract-plugin": "1.6.2", "mini-css-extract-plugin": "1.6.2",
@@ -4415,13 +3764,13 @@
"postcss": "8.4.49", "postcss": "8.4.49",
"postcss-custom-media": "10.0.8", "postcss-custom-media": "10.0.8",
"postcss-loader": "7.3.4", "postcss-loader": "7.3.4",
"postcss-rtlcss": "5.7.1", "postcss-rtlcss": "5.1.2",
"react-dev-utils": "12.0.1", "react-dev-utils": "12.0.1",
"react-refresh": "0.16.0", "react-refresh": "0.16.0",
"resolve-url-loader": "5.0.0", "resolve-url-loader": "5.0.0",
"sass": "1.85.1", "sass": "1.85.1",
"sass-loader": "13.3.3", "sass-loader": "13.3.3",
"sharp": "0.34.3", "sharp": "0.32.6",
"source-map-loader": "4.0.2", "source-map-loader": "4.0.2",
"style-loader": "3.3.4", "style-loader": "3.3.4",
"ts-jest": "29.1.4", "ts-jest": "29.1.4",
@@ -4447,7 +3796,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
"integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7", "@babel/code-frame": "^7.24.7",
@@ -4519,7 +3867,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -4552,9 +3899,9 @@
} }
}, },
"node_modules/@openedx/frontend-build/node_modules/postcss-loader/node_modules/semver": { "node_modules/@openedx/frontend-build/node_modules/postcss-loader/node_modules/semver": {
"version": "7.7.2", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -4581,7 +3928,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz",
"integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
"immutable": "^5.0.2", "immutable": "^5.0.2",
@@ -4702,9 +4048,9 @@
} }
}, },
"node_modules/@openedx/frontend-build/node_modules/ts-jest/node_modules/semver": { "node_modules/@openedx/frontend-build/node_modules/ts-jest/node_modules/semver": {
"version": "7.7.2", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -4791,11 +4137,10 @@
} }
}, },
"node_modules/@openedx/paragon": { "node_modules/@openedx/paragon": {
"version": "23.4.5", "version": "22.17.0",
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.4.5.tgz", "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.17.0.tgz",
"integrity": "sha512-baBTZDO6hdCjI+Jj3oSQaz5GkFTR2TEs94pPMOls7bc/Fsv4zQIN8xDPo4NzAVo/2+3eSuEzUz3xzBeb+94rtw==", "integrity": "sha512-MzOLQ0myaOErwumPJwxVZXTw7zJKrARtu4YMSaISF5Sz6pE1/dYz9qfRcqaraYRcJGNdbPRzOG0v3iqbZo1uHQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"workspaces": [ "workspaces": [
"example", "example",
"component-generator", "component-generator",
@@ -4804,32 +4149,20 @@
"dependent-usage-analyzer" "dependent-usage-analyzer"
], ],
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.1.18",
"@popperjs/core": "^2.11.4", "@popperjs/core": "^2.11.4",
"@tokens-studio/sd-transforms": "^1.2.4",
"axios": "^0.27.2",
"bootstrap": "^4.6.2", "bootstrap": "^4.6.2",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"child_process": "^1.0.2", "child_process": "^1.0.2",
"chroma-js": "^2.4.2",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"cli-progress": "^3.12.0",
"commander": "^9.4.1",
"email-prop-type": "^3.0.0", "email-prop-type": "^3.0.0",
"file-selector": "^0.6.0", "file-selector": "^0.6.0",
"font-awesome": "^4.7.0",
"glob": "^8.0.3", "glob": "^8.0.3",
"inquirer": "^8.2.5", "inquirer": "^8.2.5",
"js-toml": "^1.0.0",
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
"log-update": "^4.0.0",
"mailto-link": "^2.0.0", "mailto-link": "^2.0.0",
"minimist": "^1.2.8",
"ora": "^5.4.1",
"postcss": "^8.4.21",
"postcss-combine-duplicated-selectors": "^10.0.3",
"postcss-custom-media": "^9.1.2",
"postcss-import": "^15.1.0",
"postcss-map": "^0.11.0",
"postcss-minify": "^1.1.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-bootstrap": "^1.6.5", "react-bootstrap": "^1.6.5",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
@@ -4842,8 +4175,6 @@
"react-responsive": "^8.2.0", "react-responsive": "^8.2.0",
"react-table": "^7.7.0", "react-table": "^7.7.0",
"react-transition-group": "^4.4.2", "react-transition-group": "^4.4.2",
"sass": "^1.58.3",
"style-dictionary": "^4.3.2",
"tabbable": "^5.3.3", "tabbable": "^5.3.3",
"uncontrollable": "^7.2.1", "uncontrollable": "^7.2.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
@@ -4857,16 +4188,6 @@
"react-intl": "^5.25.1 || ^6.4.0" "react-intl": "^5.25.1 || ^6.4.0"
} }
}, },
"node_modules/@openedx/paragon/node_modules/axios": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.14.9",
"form-data": "^4.0.0"
}
},
"node_modules/@openedx/paragon/node_modules/brace-expansion": { "node_modules/@openedx/paragon/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -4876,15 +4197,6 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"node_modules/@openedx/paragon/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/@openedx/paragon/node_modules/glob": { "node_modules/@openedx/paragon/node_modules/glob": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
@@ -4917,34 +4229,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@openedx/paragon/node_modules/postcss-custom-media": {
"version": "9.1.5",
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.5.tgz",
"integrity": "sha512-GStyWMz7Qbo/Gtw1xVspzVSX8eipgNg4lpsO3CAeY4/A1mzok+RV6MCv3fg62trWijh/lYEj6vps4o8JcBBpDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"dependencies": {
"@csstools/cascade-layer-name-parser": "^1.0.2",
"@csstools/css-parser-algorithms": "^2.2.0",
"@csstools/css-tokenizer": "^2.1.1",
"@csstools/media-query-list-parser": "^2.1.1"
},
"engines": {
"node": "^14 || ^16 || >=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
},
"node_modules/@optimizely/js-sdk-logging": { "node_modules/@optimizely/js-sdk-logging": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz",
@@ -5447,16 +4731,6 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": { "node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz",
@@ -5516,7 +4790,6 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
@@ -5527,7 +4800,6 @@
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz",
"integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"immer": "^9.0.21", "immer": "^9.0.21",
"redux": "^4.2.1", "redux": "^4.2.1",
@@ -5799,7 +5071,6 @@
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0", "@svgr/babel-preset": "8.1.0",
@@ -5902,9 +5173,7 @@
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
@@ -5923,7 +5192,6 @@
"version": "6.6.3", "version": "6.6.3",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
"integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@adobe/css-tools": "^4.4.0", "@adobe/css-tools": "^4.4.0",
@@ -5944,7 +5212,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -5958,14 +5225,12 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@testing-library/react": { "node_modules/@testing-library/react": {
"version": "16.3.0", "version": "16.3.0",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
"integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5" "@babel/runtime": "^7.12.5"
@@ -6003,32 +5268,6 @@
"@testing-library/dom": ">=7.21.4" "@testing-library/dom": ">=7.21.4"
} }
}, },
"node_modules/@tokens-studio/sd-transforms": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz",
"integrity": "sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==",
"license": "MIT",
"dependencies": {
"@bundled-es-modules/deepmerge": "^4.3.1",
"@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6",
"@tokens-studio/types": "^0.5.1",
"colorjs.io": "^0.5.2",
"expr-eval-fork": "^2.0.2",
"is-mergeable-object": "^1.1.1"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"style-dictionary": "^4.3.0 || ^5.0.0-rc.0"
}
},
"node_modules/@tokens-studio/types": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.5.2.tgz",
"integrity": "sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==",
"license": "MIT"
},
"node_modules/@tootallnate/once": { "node_modules/@tootallnate/once": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -6061,7 +5300,6 @@
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
@@ -6473,7 +5711,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz",
"integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -6610,7 +5847,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.4.0", "@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "5.62.0",
@@ -6657,7 +5893,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0", "@typescript-eslint/types": "5.62.0",
@@ -7269,23 +6504,6 @@
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
"license": "BSD-2-Clause"
},
"node_modules/@zip.js/zip.js": {
"version": "2.7.60",
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.60.tgz",
"integrity": "sha512-vA3rLyqdxBrVo1FWSsbyoecaqWTV+vgPRf0QKeM7kVDG0r+lHUqd7zQDv1TO9k4BcAoNzNDSNrrel24Mk6addA==",
"license": "BSD-3-Clause",
"engines": {
"bun": ">=0.7.0",
"deno": ">=1.0.0",
"node": ">=16.5.0"
}
},
"node_modules/abab": { "node_modules/abab": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -7324,7 +6542,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -7400,7 +6617,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -7739,19 +6955,6 @@
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/assert": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
"integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
"is-nan": "^1.3.2",
"object-is": "^1.1.5",
"object.assign": "^4.1.4",
"util": "^0.12.5"
}
},
"node_modules/assert-ok": { "node_modules/assert-ok": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz",
@@ -7764,15 +6967,6 @@
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/async": { "node_modules/async": {
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -7884,11 +7078,10 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.9.0", "version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@@ -7896,9 +7089,9 @@
} }
}, },
"node_modules/axios-cache-interceptor": { "node_modules/axios-cache-interceptor": {
"version": "1.8.0", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.8.0.tgz", "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.6.2.tgz",
"integrity": "sha512-cTNnPGJyQkxnWp0EWvE3NRvgURU5cWw/Qx3dIhXyHSM4Ip0c7EEe0I3an0Jwa549m1CAOg57ibj27YRNLmQCcg==", "integrity": "sha512-YLbAODIHZZIcD4b3WYFVQOa5W2TY/WnJ6sBHqAg6Z+hx+RVj8/OcjQyRopO6awn7/kOkGL5X9TP16AucnlJ/lw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cache-parser": "1.2.5", "cache-parser": "1.2.5",
@@ -7938,12 +7131,17 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/b4a": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
"integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==",
"license": "Apache-2.0"
},
"node_modules/babel-jest": { "node_modules/babel-jest": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jest/transform": "^29.7.0", "@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14", "@types/babel__core": "^7.1.14",
@@ -8199,6 +7397,78 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bare-events": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz",
"integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==",
"license": "Apache-2.0",
"optional": true
},
"node_modules/bare-fs": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.2.tgz",
"integrity": "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz",
"integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz",
"integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"streamx": "^2.21.0"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -8397,7 +7667,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001688", "caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73", "electron-to-chromium": "^1.5.73",
@@ -8615,9 +7884,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001751", "version": "1.0.30001715",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
"integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -8665,12 +7934,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/change-case": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
"license": "MIT"
},
"node_modules/char-regex": { "node_modules/char-regex": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
@@ -8814,20 +8077,6 @@
"boolbase": "~1.0.0" "boolbase": "~1.0.0"
} }
}, },
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/child_process": { "node_modules/child_process": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
@@ -8858,11 +8107,11 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chroma-js": { "node_modules/chownr": {
"version": "2.6.0", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.6.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "(BSD-3-Clause AND Apache-2.0)" "license": "ISC"
}, },
"node_modules/chrome-trace-event": { "node_modules/chrome-trace-event": {
"version": "1.0.4", "version": "1.0.4",
@@ -8955,18 +8204,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/cli-progress": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
"integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
"license": "MIT",
"dependencies": {
"string-width": "^4.2.3"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cli-spinners": { "node_modules/cli-spinners": {
"version": "2.9.2", "version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
@@ -9111,12 +8348,6 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/colorjs.io": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
"license": "MIT"
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -9375,6 +8606,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/core-js": {
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.41.0", "version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
@@ -9605,7 +8848,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cssesc": { "node_modules/cssesc": {
@@ -9891,6 +9133,21 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dedent": { "node_modules/dedent": {
"version": "1.5.3", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
@@ -9905,6 +9162,15 @@
} }
} }
}, },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -10219,7 +9485,6 @@
"version": "0.5.16", "version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/dom-converter": { "node_modules/dom-converter": {
@@ -10391,12 +9656,6 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -10490,7 +9749,6 @@
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"once": "^1.4.0" "once": "^1.4.0"
@@ -10797,7 +10055,6 @@
"integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0", "@eslint-community/regexpp": "^4.4.0",
@@ -10854,7 +10111,6 @@
"resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz",
"integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"object.assign": "^4.1.2", "object.assign": "^4.1.2",
@@ -10895,7 +10151,6 @@
"resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz",
"integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"eslint-config-airbnb-base": "^15.0.0" "eslint-config-airbnb-base": "^15.0.0"
}, },
@@ -11312,7 +10567,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@rtsao/scc": "^1.1.0", "@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8", "array-includes": "^3.1.8",
@@ -11367,7 +10621,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz",
"integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.7", "@babel/runtime": "^7.20.7",
"aria-query": "^5.1.3", "aria-query": "^5.1.3",
@@ -11404,7 +10657,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
"integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"array-includes": "^3.1.6", "array-includes": "^3.1.6",
"array.prototype.flatmap": "^1.3.1", "array.prototype.flatmap": "^1.3.1",
@@ -11435,7 +10687,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.1.tgz",
"integrity": "sha512-Ck77j8hF7l9N4S/rzSLOWEKpn994YH6iwUK8fr9mXIaQvGpQYmOnQLbiue1u5kI5T1y+gdgqosnEAO9NCz0DBg==", "integrity": "sha512-Ck77j8hF7l9N4S/rzSLOWEKpn994YH6iwUK8fr9mXIaQvGpQYmOnQLbiue1u5kI5T1y+gdgqosnEAO9NCz0DBg==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@@ -11783,6 +11034,15 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/expect": { "node_modules/expect": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
@@ -11799,12 +11059,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/expr-eval-fork": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-2.0.2.tgz",
"integrity": "sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==",
"license": "MIT"
},
"node_modules/express": { "node_modules/express": {
"version": "4.21.2", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
@@ -11905,6 +11159,12 @@
"integrity": "sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==", "integrity": "sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -12219,15 +11479,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/find-yarn-workspace-root": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
"license": "Apache-2.0",
"dependencies": {
"micromatch": "^4.0.2"
}
},
"node_modules/flat": { "node_modules/flat": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
@@ -12305,6 +11556,15 @@
} }
} }
}, },
"node_modules/font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==",
"license": "(OFL-1.1 AND MIT)",
"engines": {
"node": ">=0.10.3"
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -12320,34 +11580,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/foreground-child/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fork-ts-checker-webpack-plugin": { "node_modules/fork-ts-checker-webpack-plugin": {
"version": "6.5.3", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz",
@@ -12511,6 +11743,12 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -12710,6 +11948,12 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
} }
}, },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": { "node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -12862,7 +12106,6 @@
"integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"iterall": "^1.2.2" "iterall": "^1.2.2"
}, },
@@ -13421,15 +12664,6 @@
"node": ">=10.17.0" "node": ">=10.17.0"
} }
}, },
"node_modules/hyperdyperid": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
"integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
"license": "MIT",
"engines": {
"node": ">=10.18"
}
},
"node_modules/hyphenate-style-name": { "node_modules/hyphenate-style-name": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
@@ -13514,16 +12748,16 @@
} }
}, },
"node_modules/image-minimizer-webpack-plugin": { "node_modules/image-minimizer-webpack-plugin": {
"version": "4.1.4", "version": "3.8.3",
"resolved": "https://registry.npmjs.org/image-minimizer-webpack-plugin/-/image-minimizer-webpack-plugin-4.1.4.tgz", "resolved": "https://registry.npmjs.org/image-minimizer-webpack-plugin/-/image-minimizer-webpack-plugin-3.8.3.tgz",
"integrity": "sha512-A2DLYuCyu7icbGdv8OMGFQKPXvsztWAueBkT3yQ7KVW1YGnAJKtgLYELkN7/aUday05DzEdKRaLE5Bnh/9S2UQ==", "integrity": "sha512-Ex0cjNJc2FUSuwN7WHNyxkIZINP0M9lrN+uWJznMcsehiM5Z7ELwk+SEkSGEookK1GUd2wf+09jy1PEH5a5XmQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"schema-utils": "^4.2.0", "schema-utils": "^4.2.0",
"serialize-javascript": "^6.0.2" "serialize-javascript": "^6.0.1"
}, },
"engines": { "engines": {
"node": ">= 18.12.0" "node": ">= 12.13.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -13650,7 +12884,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -13806,22 +13039,6 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -14152,28 +13369,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-mergeable-object": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz",
"integrity": "sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==",
"license": "MIT"
},
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -14623,21 +13818,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jake": { "node_modules/jake": {
"version": "10.9.2", "version": "10.9.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
@@ -15166,7 +14346,6 @@
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.12.13", "@babel/code-frame": "^7.12.13",
"@jest/types": "^29.6.3", "@jest/types": "^29.6.3",
@@ -15613,16 +14792,6 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-toml": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/js-toml/-/js-toml-1.0.2.tgz",
"integrity": "sha512-/7IQ//bzn2a/5IDazPUNzlW7bsjxS51cxciYZDR+Z+3Le60yzT0YfI8KOWqTtBcZkXXVklhWd2OuGd8ZksB0wQ==",
"license": "MIT",
"dependencies": {
"chevrotain": "^11.0.3",
"xregexp": "^5.1.1"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
@@ -15868,15 +15037,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.11"
}
},
"node_modules/kleur": { "node_modules/kleur": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -16018,12 +15178,6 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash.assignin": { "node_modules/lodash.assignin": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
@@ -16178,24 +15332,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/log-update": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
"integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
"license": "MIT",
"dependencies": {
"ansi-escapes": "^4.3.0",
"cli-cursor": "^3.1.0",
"slice-ansi": "^4.0.0",
"wrap-ansi": "^6.2.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/loose-envify": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -16230,7 +15366,6 @@
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"lz-string": "bin/bin.js" "lz-string": "bin/bin.js"
@@ -16969,11 +16104,22 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/min-indent": { "node_modules/min-indent": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -17045,14 +16191,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minipass": { "node_modules/mkdirp-classic": {
"version": "7.1.2", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "ISC", "license": "MIT"
"engines": {
"node": ">=16 || 14 >=14.17"
}
}, },
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
@@ -17123,6 +16266,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/napi-postinstall": { "node_modules/napi-postinstall": {
"version": "0.1.6", "version": "0.1.6",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.1.6.tgz", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.1.6.tgz",
@@ -17175,6 +16324,30 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/node-abi": {
"version": "3.74.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": { "node_modules/node-addon-api": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
@@ -17319,22 +16492,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": { "node_modules/object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -17672,12 +16829,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/param-case": { "node_modules/param-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
@@ -17749,95 +16900,6 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/patch-package": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
"license": "MIT",
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2",
"ci-info": "^3.7.0",
"cross-spawn": "^7.0.3",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^9.0.0",
"json-stable-stringify": "^1.0.2",
"klaw-sync": "^6.0.0",
"minimist": "^1.2.6",
"open": "^7.4.2",
"rimraf": "^2.6.3",
"semver": "^7.5.3",
"slash": "^2.0.0",
"tmp": "^0.0.33",
"yaml": "^2.2.2"
},
"bin": {
"patch-package": "index.js"
},
"engines": {
"node": ">=14",
"npm": ">5"
}
},
"node_modules/patch-package/node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/patch-package/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/patch-package/node_modules/slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/patch-package/node_modules/yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/path": {
"version": "0.12.7",
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
"integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
"license": "MIT",
"dependencies": {
"process": "^0.11.1",
"util": "^0.10.3"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -17877,28 +16939,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.12", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
@@ -17914,27 +16954,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/path-unified": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/path-unified/-/path-unified-0.2.0.tgz",
"integrity": "sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==",
"license": "MIT"
},
"node_modules/path/node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
"license": "ISC"
},
"node_modules/path/node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
"license": "MIT",
"dependencies": {
"inherits": "2.0.3"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -18313,7 +17332,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.8", "nanoid": "^3.3.8",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -18339,24 +17357,6 @@
"postcss": "^8.2.2" "postcss": "^8.2.2"
} }
}, },
"node_modules/postcss-calc-ast-parser": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.4.tgz",
"integrity": "sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==",
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^3.3.1"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/postcss-calc-ast-parser/node_modules/postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"license": "MIT"
},
"node_modules/postcss-colormin": { "node_modules/postcss-colormin": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz",
@@ -18375,21 +17375,6 @@
"postcss": "^8.4.31" "postcss": "^8.4.31"
} }
}, },
"node_modules/postcss-combine-duplicated-selectors": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz",
"integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==",
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^6.0.4"
},
"engines": {
"node": "^10.0.0 || ^12.0.0 || >=14.0.0"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-convert-values": { "node_modules/postcss-convert-values": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz",
@@ -18482,23 +17467,6 @@
"postcss": "^8.4.31" "postcss": "^8.4.31"
} }
}, },
"node_modules/postcss-import": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-loader": { "node_modules/postcss-loader": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz",
@@ -18586,52 +17554,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/postcss-map": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/postcss-map/-/postcss-map-0.11.0.tgz",
"integrity": "sha512-cgHYZrH9aAMds90upYUPhYz8xnAcRD45SwuNns/nQHONIrPQDhpwk3JLsAQGOndQxnRVXfB6nB+3WqSMy8fqlA==",
"license": "Unlicense",
"dependencies": {
"js-yaml": "^3.12.0",
"postcss": "^7.0.2",
"reduce-function-call": "^1.0.1"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/postcss-map/node_modules/picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
"license": "ISC"
},
"node_modules/postcss-map/node_modules/postcss": {
"version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"license": "MIT",
"dependencies": {
"picocolors": "^0.2.1",
"source-map": "^0.6.1"
},
"engines": {
"node": ">=6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-map/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==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postcss-merge-longhand": { "node_modules/postcss-merge-longhand": {
"version": "6.0.5", "version": "6.0.5",
"resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz",
@@ -18666,19 +17588,6 @@
"postcss": "^8.4.31" "postcss": "^8.4.31"
} }
}, },
"node_modules/postcss-minify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/postcss-minify/-/postcss-minify-1.1.0.tgz",
"integrity": "sha512-9D64ueIW0DL2FdLajQTlXrnTN8Ox9NjuXqigKMmB819RhdClNPYx5Zp3i5x0ghjjy3vGrLBBYEYvJjY/1eMNbw==",
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^6.0",
"postcss-value-parser": "^4.1"
},
"peerDependencies": {
"postcss": "^8.0"
}
},
"node_modules/postcss-minify-font-values": { "node_modules/postcss-minify-font-values": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz",
@@ -19009,12 +17918,12 @@
} }
}, },
"node_modules/postcss-rtlcss": { "node_modules/postcss-rtlcss": {
"version": "5.7.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/postcss-rtlcss/-/postcss-rtlcss-5.7.1.tgz", "resolved": "https://registry.npmjs.org/postcss-rtlcss/-/postcss-rtlcss-5.1.2.tgz",
"integrity": "sha512-zE68CuARv5StOG/UQLa0W1Y/raUTzgJlfjtas43yh3/G1BFmoPEaHxPRHgeowXRFFhW33FehrNgsljxRLmPVWw==", "integrity": "sha512-cmcgRoO1wL7IJyVHw0RneWI/5Oe75NLC2NLlQLsNI7hcui+yRcW4RrILfQa4FqKQRLTU4r5eF0YPi1qZpMzQpA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"rtlcss": "4.3.0" "rtlcss": "4.1.1"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@@ -19073,6 +17982,69 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prebuild-install/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/prebuild-install/node_modules/tar-fs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/prebuild-install/node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -19082,21 +18054,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-error": { "node_modules/pretty-error": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
@@ -19111,7 +18068,6 @@
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1", "ansi-regex": "^5.0.1",
@@ -19126,7 +18082,6 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -19139,13 +18094,13 @@
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">= 0.6.0"
@@ -19182,7 +18137,6 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@@ -19253,7 +18207,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"end-of-stream": "^1.1.0", "end-of-stream": "^1.1.0",
@@ -19474,12 +18427,35 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -19633,7 +18609,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -19948,7 +18923,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.15.4", "@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20", "@types/react-redux": "^7.1.20",
@@ -19980,7 +18954,6 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz",
"integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -20055,7 +19028,6 @@
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz",
"integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==", "integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@remix-run/router": "1.8.0" "@remix-run/router": "1.8.0"
}, },
@@ -20071,7 +19043,6 @@
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz",
"integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==", "integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@remix-run/router": "1.8.0", "@remix-run/router": "1.8.0",
"react-router": "6.15.0" "react-router": "6.15.0"
@@ -20084,6 +19055,19 @@
"react-dom": ">=16.8" "react-dom": ">=16.8"
} }
}, },
"node_modules/react-shallow-renderer": {
"version": "16.15.0",
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz",
"integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==",
"license": "MIT",
"dependencies": {
"object-assign": "^4.1.1",
"react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-share": { "node_modules/react-share": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-share/-/react-share-4.4.1.tgz", "resolved": "https://registry.npmjs.org/react-share/-/react-share-4.4.1.tgz",
@@ -20145,6 +19129,26 @@
"react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0"
} }
}, },
"node_modules/react-test-renderer": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz",
"integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==",
"license": "MIT",
"dependencies": {
"react-is": "^18.3.1",
"react-shallow-renderer": "^16.15.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/react-test-renderer/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-transition-group": { "node_modules/react-transition-group": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -20161,24 +19165,6 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
}
},
"node_modules/read-cache/node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/read-pkg": { "node_modules/read-pkg": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -20297,7 +19283,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"indent-string": "^4.0.0", "indent-string": "^4.0.0",
@@ -20307,21 +19292,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/reduce-function-call": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
"integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/redux": { "node_modules/redux": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.9.2" "@babel/runtime": "^7.9.2"
} }
@@ -20708,9 +19683,9 @@
} }
}, },
"node_modules/rtlcss": { "node_modules/rtlcss": {
"version": "4.3.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz",
"integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"escalade": "^3.1.1", "escalade": "^3.1.1",
@@ -20999,7 +19974,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1", "fast-uri": "^3.0.1",
@@ -21285,45 +20259,26 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.34.3", "version": "0.32.6",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
"integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"color": "^4.2.3", "color": "^4.2.3",
"detect-libc": "^2.0.4", "detect-libc": "^2.0.2",
"semver": "^7.7.2" "node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=14.15.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.3",
"@img/sharp-darwin-x64": "0.34.3",
"@img/sharp-libvips-darwin-arm64": "1.2.0",
"@img/sharp-libvips-darwin-x64": "1.2.0",
"@img/sharp-libvips-linux-arm": "1.2.0",
"@img/sharp-libvips-linux-arm64": "1.2.0",
"@img/sharp-libvips-linux-ppc64": "1.2.0",
"@img/sharp-libvips-linux-s390x": "1.2.0",
"@img/sharp-libvips-linux-x64": "1.2.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
"@img/sharp-libvips-linuxmusl-x64": "1.2.0",
"@img/sharp-linux-arm": "0.34.3",
"@img/sharp-linux-arm64": "0.34.3",
"@img/sharp-linux-ppc64": "0.34.3",
"@img/sharp-linux-s390x": "0.34.3",
"@img/sharp-linux-x64": "0.34.3",
"@img/sharp-linuxmusl-arm64": "0.34.3",
"@img/sharp-linuxmusl-x64": "0.34.3",
"@img/sharp-wasm32": "0.34.3",
"@img/sharp-win32-arm64": "0.34.3",
"@img/sharp-win32-ia32": "0.34.3",
"@img/sharp-win32-x64": "0.34.3"
} }
}, },
"node_modules/sharp/node_modules/detect-libc": { "node_modules/sharp/node_modules/detect-libc": {
@@ -21335,10 +20290,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/sharp/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"license": "MIT"
},
"node_modules/sharp/node_modules/semver": { "node_modules/sharp/node_modules/semver": {
"version": "7.7.2", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -21458,6 +20419,51 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-swizzle": { "node_modules/simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -21502,23 +20508,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
"integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/snake-case": { "node_modules/snake-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
@@ -21777,25 +20766,17 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/stream": { "node_modules/streamx": {
"version": "0.0.3", "version": "2.22.0",
"resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
"integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"component-emitter": "^2.0.0" "fast-fifo": "^1.3.2",
} "text-decoder": "^1.1.0"
},
"node_modules/stream/node_modules/component-emitter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz",
"integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==",
"license": "MIT",
"engines": {
"node": ">=18"
}, },
"funding": { "optionalDependencies": {
"url": "https://github.com/sponsors/sindresorhus" "bare-events": "^2.2.0"
} }
}, },
"node_modules/strict-uri-encode": { "node_modules/strict-uri-encode": {
@@ -21843,27 +20824,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width/node_modules/emoji-regex": { "node_modules/string-width/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -21965,19 +20925,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-bom": { "node_modules/strip-bom": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
@@ -22000,7 +20947,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"min-indent": "^1.0.0" "min-indent": "^1.0.0"
@@ -22021,56 +20967,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/style-dictionary": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/style-dictionary/-/style-dictionary-4.4.0.tgz",
"integrity": "sha512-+xU0IA1StzqAqFs/QtXkK+XJa7wpS4X5H+JQccRKsRCElgeLGocFU1U/UMvMUylKFw6vwGV+Y/a2wb2pm5rFFQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@bundled-es-modules/deepmerge": "^4.3.1",
"@bundled-es-modules/glob": "^10.4.2",
"@bundled-es-modules/memfs": "^4.9.4",
"@zip.js/zip.js": "^2.7.44",
"chalk": "^5.3.0",
"change-case": "^5.3.0",
"commander": "^12.1.0",
"is-plain-obj": "^4.1.0",
"json5": "^2.2.2",
"patch-package": "^8.0.0",
"path-unified": "^0.2.0",
"prettier": "^3.3.3",
"tinycolor2": "^1.6.0"
},
"bin": {
"style-dictionary": "bin/style-dictionary.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/style-dictionary/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/style-dictionary/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/style-loader": { "node_modules/style-loader": {
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
@@ -22289,6 +21185,31 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/tar-fs": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
},
"optionalDependencies": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0"
}
},
"node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/terser": { "node_modules/terser": {
"version": "5.39.0", "version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
@@ -22409,24 +21330,21 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/text-decoder": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
"integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/text-table": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/thingies": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
"integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
"license": "Unlicense",
"engines": {
"node": ">=10.18"
},
"peerDependencies": {
"tslib": "^2"
}
},
"node_modules/thread-stream": { "node_modules/thread-stream": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz",
@@ -22461,12 +21379,6 @@
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
"license": "MIT"
},
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.13", "version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
@@ -22502,7 +21414,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -22594,22 +21505,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/tree-dump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
"integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/trim-lines": { "node_modules/trim-lines": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -22822,8 +21717,7 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD", "license": "0BSD"
"peer": true
}, },
"node_modules/tsutils": { "node_modules/tsutils": {
"version": "3.21.0", "version": "3.21.0",
@@ -22846,6 +21740,18 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -22872,7 +21778,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"license": "(MIT OR CC0-1.0)", "license": "(MIT OR CC0-1.0)",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@@ -23292,19 +22197,6 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/url": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
"integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
"license": "MIT",
"dependencies": {
"punycode": "^1.4.1",
"qs": "^6.12.3"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/url-loader": { "node_modules/url-loader": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
@@ -23360,12 +22252,6 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"node_modules/url/node_modules/punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
"license": "MIT"
},
"node_modules/use-callback-ref": { "node_modules/use-callback-ref": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -23418,19 +22304,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -23642,7 +22515,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz",
"integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==", "integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.7", "@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6", "@types/estree": "^1.0.6",
@@ -23745,7 +22617,6 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@discoveryjs/json-ext": "^0.5.0", "@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1", "@webpack-cli/configtest": "^2.1.1",
@@ -23832,7 +22703,6 @@
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
"integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/bonjour": "^3.5.9", "@types/bonjour": "^3.5.9",
"@types/connect-history-api-fallback": "^1.3.5", "@types/connect-history-api-fallback": "^1.3.5",
@@ -24193,24 +23063,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -24266,15 +23118,6 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/xregexp": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz",
"integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==",
"license": "MIT",
"dependencies": {
"@babel/runtime-corejs3": "^7.26.9"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@@ -15,10 +15,10 @@
"i18n_extract": "fedx-scripts formatjs extract", "i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .", "lint": "fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx .", "lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress", "start": "fedx-scripts webpack-dev-server --progress",
"start:with-theme": "paragon install-theme && npm start && npm install",
"dev": "PUBLIC_PATH=/learning/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io", "dev": "PUBLIC_PATH=/learning/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "NODE_ENV=test fedx-scripts jest --coverage --passWithNoTests", "test": "fedx-scripts jest --coverage --passWithNoTests",
"test:watch": "fedx-scripts jest --watch --passWithNoTests", "test:watch": "fedx-scripts jest --watch --passWithNoTests",
"types": "tsc --noEmit" "types": "tsc --noEmit"
}, },
@@ -35,18 +35,19 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "1.5.0", "@edx/browserslist-config": "1.5.0",
"@edx/frontend-component-footer": "^14.6.0", "@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^8.0.0", "@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-lib-learning-assistant": "^2.23.1", "@edx/frontend-lib-learning-assistant": "^2.20.0",
"@edx/frontend-lib-special-exams": "^4.0.0", "@edx/frontend-lib-special-exams": "^3.5.0",
"@edx/frontend-platform": "^8.4.0", "@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.7.0", "@edx/openedx-atlas": "^0.6.0",
"@edx/react-unit-test-utils": "^4.0.0",
"@fortawesome/free-brands-svg-icons": "5.15.4", "@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.4", "@fortawesome/react-fontawesome": "^0.1.4",
"@openedx/frontend-build": "^14.6.2", "@openedx/frontend-build": "^14.5.0",
"@openedx/frontend-plugin-framework": "^1.7.0", "@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/paragon": "^23.4.5", "@openedx/paragon": "^22.16.0",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@reduxjs/toolkit": "1.9.7", "@reduxjs/toolkit": "1.9.7",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -74,6 +75,7 @@
"truncate-html": "1.0.4" "truncate-html": "1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@edx/reactifex": "2.2.0",
"@pact-foundation/pact": "^13.0.0", "@pact-foundation/pact": "^13.0.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",

View File

@@ -0,0 +1,224 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
<React Strict Mode>
<ErrorPage
message="test-error-message"
/>
</React Strict Mode>
`;
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
<React Strict Mode>
<AppProvider
store={
{
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<HelmetWrapper
defer={true}
encodeSpecialCharacters={true}
>
<link
href="favicon-url"
rel="shortcut icon"
type="image/x-icon"
/>
</HelmetWrapper>
<PathFixesProvider>
<NoticesProvider>
<UserMessagesProvider>
<Routes>
<Route
element={
<PageWrap>
<Page Not Found />
</PageWrap>
}
path="*"
/>
<Route
element={
<PageWrap>
<Goal Unsubscribe />
</PageWrap>
}
path="/goal-unsubscribe/:token"
/>
<Route
element={
<PageWrap>
<Courseware Redirect Landing Page />
</PageWrap>
}
path="/redirect/*"
/>
<Route
element={
<PageWrap>
<Preferences Unsubscribe />
</PageWrap>
}
path="/preferences-unsubscribe/:userToken/:updatePatch"
/>
<Route
element={
<DecodePageRoute>
<Course Access Error Page />
</DecodePageRoute>
}
path="/course/:courseId/access-denied"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
slice="courseHome"
tab="outline"
>
<Outline Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/home"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
slice="courseHome"
tab="lti_live"
>
<Live Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/live"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
slice="courseHome"
tab="dates"
>
<Dates Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/dates"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
slice="courseHome"
tab="discussion"
>
<Discussion Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/discussion/:path/*"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
isProgressTab={true}
slice="courseHome"
tab="progress"
>
<Progress Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/progress/:targetUserId/"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
isProgressTab={true}
slice="courseHome"
tab="progress"
>
<Progress Tab />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/progress"
/>
<Route
element={
<DecodePageRoute>
<Tab Container
fetch={[Function]}
slice="courseware"
tab="courseware"
>
<Course Exit />
</Tab Container>
</DecodePageRoute>
}
path="/course/:courseId/course-end"
/>
<Route
element={
<DecodePageRoute>
<Courseware Container />
</DecodePageRoute>
}
path="/course/:courseId/:sequenceId/:unitId"
/>
<Route
element={
<DecodePageRoute>
<Courseware Container />
</DecodePageRoute>
}
path="/course/:courseId/:sequenceId"
/>
<Route
element={
<DecodePageRoute>
<Courseware Container />
</DecodePageRoute>
}
path="/course/:courseId"
/>
<Route
element={
<DecodePageRoute>
<Courseware Container />
</DecodePageRoute>
}
path="/preview/course/:courseId/:sequenceId/:unitId"
/>
<Route
element={
<DecodePageRoute>
<Courseware Container />
</DecodePageRoute>
}
path="/preview/course/:courseId/:sequenceId"
/>
</Routes>
</UserMessagesProvider>
</NoticesProvider>
</PathFixesProvider>
</AppProvider>
</React Strict Mode>
`;

View File

@@ -22,7 +22,7 @@ export const DECODE_ROUTES = {
export const ROUTES = { export const ROUTES = {
UNSUBSCRIBE: '/goal-unsubscribe/:token', UNSUBSCRIBE: '/goal-unsubscribe/:token',
PREFERENCES_UNSUBSCRIBE: '/preferences-unsubscribe/:userToken/:updatePatch?', PREFERENCES_UNSUBSCRIBE: '/preferences-unsubscribe/:userToken/:updatePatch',
REDIRECT: '/redirect/*', REDIRECT: '/redirect/*',
DASHBOARD: 'dashboard', DASHBOARD: 'dashboard',
ENTERPRISE_LEARNER_DASHBOARD: 'enterprise-learner-dashboard', ENTERPRISE_LEARNER_DASHBOARD: 'enterprise-learner-dashboard',

View File

@@ -5,7 +5,6 @@ import {
screen, screen,
} from '../../setupTest'; } from '../../setupTest';
import CoursewareSearchEmpty from './CoursewareSearchEmpty'; import CoursewareSearchEmpty from './CoursewareSearchEmpty';
import messages from './messages';
function renderComponent() { function renderComponent() {
const { container } = render(<CoursewareSearchEmpty />); const { container } = render(<CoursewareSearchEmpty />);
@@ -17,12 +16,9 @@ describe('CoursewareSearchEmpty', () => {
initializeMockApp(); initializeMockApp();
}); });
it('render empty results text and corresponding classes', () => { it('should match the snapshot', () => {
renderComponent(); renderComponent();
const emptyText = screen.getByText(messages.searchResultsNone.defaultMessage);
expect(emptyText).toBeInTheDocument(); expect(screen.getByTestId('no-results')).toMatchSnapshot();
expect(emptyText).toHaveClass('courseware-search-results__empty');
expect(emptyText).toHaveAttribute('data-testid', 'no-results');
expect(emptyText.parentElement).toHaveClass('courseware-search-results');
}); });
}); });

View File

@@ -7,7 +7,6 @@ import {
import CoursewareSearchResults from './CoursewareSearchResults'; import CoursewareSearchResults from './CoursewareSearchResults';
import messages from './messages'; import messages from './messages';
import searchResultsFactory from './test-data/search-results-factory'; import searchResultsFactory from './test-data/search-results-factory';
import * as mock from './test-data/mocked-response.json';
jest.mock('react-redux'); jest.mock('react-redux');
@@ -35,53 +34,8 @@ describe('CoursewareSearchResults', () => {
renderComponent({ results }); renderComponent({ results });
}); });
it('should render complete list', () => { it('should match the snapshot', () => {
const courses = screen.getAllByRole('link'); expect(screen.getByTestId('search-results')).toMatchSnapshot();
expect(courses.length).toBe(mock.results.length);
});
it('should render correct link for internal course', () => {
const courses = screen.getAllByRole('link');
const firstCourse = courses[0];
const firstCourseTitle = firstCourse.querySelector('.courseware-search-results__title span');
expect(firstCourseTitle.innerHTML).toEqual(mock.results[0].data.content.display_name);
expect(firstCourse.href).toContain(mock.results[0].data.url);
expect(firstCourse).not.toHaveAttribute('target', '_blank');
expect(firstCourse).not.toHaveAttribute('rel', 'nofollow');
});
it('should render correct link if is External url course', () => {
const courses = screen.getAllByRole('link');
const externalCourse = courses[courses.length - 1];
const externalCourseTitle = externalCourse.querySelector('.courseware-search-results__title span');
expect(externalCourseTitle.innerHTML).toEqual(mock.results[mock.results.length - 1].data.content.display_name);
expect(externalCourse.href).toContain(mock.results[mock.results.length - 1].data.url);
expect(externalCourse).toHaveAttribute('target', '_blank');
expect(externalCourse).toHaveAttribute('rel', 'nofollow');
const icon = externalCourse.querySelector('svg');
expect(icon).toBeInTheDocument();
});
it('should render location breadcrumbs', () => {
const breadcrumbs = screen.getAllByText(mock.results[0].data.location[0]);
expect(breadcrumbs.length).toBeGreaterThan(0);
const firstBreadcrumb = breadcrumbs[0].closest('li');
expect(firstBreadcrumb).toBeInTheDocument();
expect(firstBreadcrumb.querySelector('div').textContent).toBe(mock.results[0].data.location[0]);
expect(firstBreadcrumb.nextSibling.querySelector('div').textContent).toBe(mock.results[0].data.location[1]);
});
});
describe('when results are provided with content hits', () => {
beforeEach(() => {
const { results } = searchResultsFactory('Passing');
renderComponent({ results });
});
it('should render content hits', () => {
const contentHits = screen.getByText('1');
expect(contentHits).toBeInTheDocument();
expect(contentHits.tagName).toBe('EM');
}); });
}); });
}); });

View File

@@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CoursewareSearchEmpty should match the snapshot 1`] = `
<p
class="courseware-search-results__empty"
data-testid="no-results"
>
No results found.
</p>
`;

View File

@@ -0,0 +1,1238 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CoursewareSearchResults when list of results is provided should match the snapshot 1`] = `
<div
class="courseware-search-results"
data-testid="search-results"
>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 4H2v16h20V6H12l-2-2z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Demo Course Overview
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Introduction
</div>
</li>
<li>
<div>
Demo Course Overview
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@5e009378f0b64585baa0a14b155974b9"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Passing a Course
</span>
<em>
1
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
About Exams and Certificates
</div>
</li>
<li>
<div>
edX Exams
</div>
</li>
<li>
<div>
Passing a Course
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 4H2v16h20V6H12l-2-2z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Passing a Course
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
About Exams and Certificates
</div>
</li>
<li>
<div>
edX Exams
</div>
</li>
<li>
<div>
Passing a Course
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@0d759dee4f9d459c8956136dbde55f02"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Text Input
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Homework - Question Styles
</div>
</li>
<li>
<div>
Text input
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@c554538a57664fac80783b99d9d6da7c"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Pointing on a Picture
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Homework - Question Styles
</div>
</li>
<li>
<div>
Pointing on a Picture
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@45d46192272c4f6db6b63586520bbdf4"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Getting Answers
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
About Exams and Certificates
</div>
</li>
<li>
<div>
edX Exams
</div>
</li>
<li>
<div>
Getting Answers
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@0b9e39477cf34507a7a48f74be381fdd"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 10.5V6H3v12h14v-4.5l4 4v-11l-4 4Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Welcome!
</span>
<em>
30
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Introduction
</div>
</li>
<li>
<div>
Demo Course Overview
</div>
</li>
<li>
<div>
Introduction: Video and Sequences
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Multiple Choice Questions
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Homework - Question Styles
</div>
</li>
<li>
<div>
Multiple Choice Questions
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@75f9562c77bc4858b61f907bb810d974"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Numerical Input
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Homework - Question Styles
</div>
</li>
<li>
<div>
Numerical Input
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@636541acbae448d98ab484b028c9a7f6"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 10.5V6H3v12h14v-4.5l4 4v-11l-4 4Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Connecting a Circuit and a Circuit Diagram
</span>
<em>
3
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Lesson 1 - Getting Started
</div>
</li>
<li>
<div>
Video Presentation Styles
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@python_grader"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
CAPA
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 2: Get Interactive
</div>
</li>
<li>
<div>
Homework - Labs and Demos
</div>
</li>
<li>
<div>
Code Grader
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@9cee77a606ea4c1aa5440e0ea5d0f618"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Interactive Questions
</span>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Lesson 1 - Getting Started
</div>
</li>
<li>
<div>
Interactive Questions
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Blank HTML Page
</span>
<em>
6
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Introduction
</div>
</li>
<li>
<div>
Demo Course Overview
</div>
</li>
<li>
<div>
Introduction: Video and Sequences
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@html_49b4494da2f7"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Discussion Forums
</span>
<em>
5
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 3: Be Social
</div>
</li>
<li>
<div>
Lesson 3 - Be Social
</div>
</li>
<li>
<div>
Discussion Forums
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f4a39219742149f781a1dda6f43a623c"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Overall Grade
</span>
<em>
7
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
About Exams and Certificates
</div>
</li>
<li>
<div>
edX Exams
</div>
</li>
<li>
<div>
Overall Grade Performance
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@87fa6792d79f4862be098e5169e93339"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Blank HTML Page
</span>
<em>
3
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 3: Be Social
</div>
</li>
<li>
<div>
Lesson 3 - Be Social
</div>
</li>
<li>
<div>
Homework - Find Your Study Buddy
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@6018785795994726950614ce7d0f38c5"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Find Your Study Buddy
</span>
<em>
3
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 3: Be Social
</div>
</li>
<li>
<div>
Homework - Find Your Study Buddy
</div>
</li>
<li>
<div>
Homework - Find Your Study Buddy
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f9f3a25e7bab46e583fd1fbbd7a2f6a0"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
Be Social
</span>
<em>
4
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 3: Be Social
</div>
</li>
<li>
<div>
Lesson 3 - Be Social
</div>
</li>
<li>
<div>
Be Social
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@8293139743f34377817d537b69911530"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
EdX Exams
</span>
<em>
4
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
About Exams and Certificates
</div>
</li>
<li>
<div>
edX Exams
</div>
</li>
<li>
<div>
EdX Exams
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@9d5104b502f24ee89c3d2f4ce9d347cf"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
When Are Your Exams?
</span>
<em>
2
</em>
</div>
<ul
class="courseware-search-results__breadcrumbs"
>
<li>
<div>
Example Week 1: Getting Started
</div>
</li>
<li>
<div>
Lesson 1 - Getting Started
</div>
</li>
<li>
<div>
When Are Your Exams?
</div>
</li>
</ul>
</div>
</a>
<a
class="courseware-search-results__item"
href="https://www.edx.org"
rel="nofollow"
target="_blank"
>
<div
class="courseware-search-results__icon"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="courseware-search-results__info"
>
<div
class="courseware-search-results__title"
>
<span>
External Course Link Test
</span>
</div>
</div>
</a>
</div>
`;

View File

@@ -0,0 +1,306 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`mapSearchResponse when the response is correct should match snapshot 1`] = `
{
"filters": [
{
"count": 7,
"key": "capa",
"label": "CAPA",
},
{
"count": 2,
"key": "sequence",
"label": "Sequence",
},
{
"count": 9,
"key": "text",
"label": "Text",
},
{
"count": 1,
"key": "unknown",
"label": "Unknown",
},
{
"count": 2,
"key": "video",
"label": "Video",
},
],
"maxScore": 3.4545178,
"ms": 5,
"results": [
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
"location": [
"Introduction",
"Demo Course Overview",
],
"score": 3.4545178,
"title": "Demo Course Overview",
"type": "sequence",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@5e009378f0b64585baa0a14b155974b9",
"location": [
"About Exams and Certificates",
"edX Exams",
"Passing a Course",
],
"score": 3.4545178,
"title": "Passing a Course",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@5e009378f0b64585baa0a14b155974b9",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff",
"location": [
"About Exams and Certificates",
"edX Exams",
"Passing a Course",
],
"score": 3.4545178,
"title": "Passing a Course",
"type": "sequence",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@0d759dee4f9d459c8956136dbde55f02",
"location": [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Text input",
],
"score": 1.5874016,
"title": "Text Input",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@0d759dee4f9d459c8956136dbde55f02",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@c554538a57664fac80783b99d9d6da7c",
"location": [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Pointing on a Picture",
],
"score": 1.5499392,
"title": "Pointing on a Picture",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@c554538a57664fac80783b99d9d6da7c",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@45d46192272c4f6db6b63586520bbdf4",
"location": [
"About Exams and Certificates",
"edX Exams",
"Getting Answers",
],
"score": 1.5003732,
"title": "Getting Answers",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@45d46192272c4f6db6b63586520bbdf4",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@video+block@0b9e39477cf34507a7a48f74be381fdd",
"location": [
"Introduction",
"Demo Course Overview",
"Introduction: Video and Sequences",
],
"score": 1.4792063,
"title": "Welcome!",
"type": "video",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@0b9e39477cf34507a7a48f74be381fdd",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4",
"location": [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Multiple Choice Questions",
],
"score": 1.4341705,
"title": "Multiple Choice Questions",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@75f9562c77bc4858b61f907bb810d974",
"location": [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Numerical Input",
],
"score": 1.2987298,
"title": "Numerical Input",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@75f9562c77bc4858b61f907bb810d974",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@video+block@636541acbae448d98ab484b028c9a7f6",
"location": [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"Video Presentation Styles",
],
"score": 1.1870136,
"title": "Connecting a Circuit and a Circuit Diagram",
"type": "video",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@636541acbae448d98ab484b028c9a7f6",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@python_grader",
"location": [
"Example Week 2: Get Interactive",
"Homework - Labs and Demos",
"Code Grader",
],
"score": 1.0107487,
"title": "CAPA",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@python_grader",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@9cee77a606ea4c1aa5440e0ea5d0f618",
"location": [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"Interactive Questions",
],
"score": 0.96387196,
"title": "Interactive Questions",
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@9cee77a606ea4c1aa5440e0ea5d0f618",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4",
"location": [
"Introduction",
"Demo Course Overview",
"Introduction: Video and Sequences",
],
"score": 0.8844358,
"title": "Blank HTML Page",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@html_49b4494da2f7",
"location": [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Discussion Forums",
],
"score": 0.8803684,
"title": "Discussion Forums",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@html_49b4494da2f7",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@f4a39219742149f781a1dda6f43a623c",
"location": [
"About Exams and Certificates",
"edX Exams",
"Overall Grade Performance",
],
"score": 0.87981963,
"title": "Overall Grade",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f4a39219742149f781a1dda6f43a623c",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@87fa6792d79f4862be098e5169e93339",
"location": [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Homework - Find Your Study Buddy",
],
"score": 0.84284115,
"title": "Blank HTML Page",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@87fa6792d79f4862be098e5169e93339",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@6018785795994726950614ce7d0f38c5",
"location": [
"Example Week 3: Be Social",
"Homework - Find Your Study Buddy",
"Homework - Find Your Study Buddy",
],
"score": 0.84284115,
"title": "Find Your Study Buddy",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@6018785795994726950614ce7d0f38c5",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@f9f3a25e7bab46e583fd1fbbd7a2f6a0",
"location": [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Be Social",
],
"score": 0.84210813,
"title": "Be Social",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f9f3a25e7bab46e583fd1fbbd7a2f6a0",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@8293139743f34377817d537b69911530",
"location": [
"About Exams and Certificates",
"edX Exams",
"EdX Exams",
],
"score": 0.8306555,
"title": "EdX Exams",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@8293139743f34377817d537b69911530",
},
{
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@9d5104b502f24ee89c3d2f4ce9d347cf",
"location": [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"When Are Your Exams? ",
],
"score": 0.82610154,
"title": "When Are Your Exams? ",
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@9d5104b502f24ee89c3d2f4ce9d347cf",
},
{
"contentHits": 0,
"id": "random-element-id",
"location": null,
"score": 0.82610154,
"title": "External Course Link Test",
"type": "unknown",
"url": "https://www.edx.org",
},
],
"total": 29,
}
`;

View File

@@ -9,8 +9,8 @@
height: 100%; height: 100%;
max-width: none; max-width: none;
margin: 0; margin: 0;
border-top: 1px solid var(--pgn-color-light-300); border-top: 1px solid $light-300;
z-index: var(--pgn-elevation-modal-zindex); // Bootstrap's z-index layer for Modals. z-index: $zindex-modal; // Bootstrap's z-index layer for Modals.
&__form { &__form {
position: relative; position: relative;
@@ -47,7 +47,7 @@
&__results-summary { &__results-summary {
font-size: .9rem; font-size: .9rem;
color: var(--pgn-color-gray-500); color: $gray-500;
padding: 1rem 0 .5rem; padding: 1rem 0 .5rem;
} }
@@ -62,7 +62,7 @@
margin-top: 1.5rem; margin-top: 1.5rem;
&__empty { &__empty {
color: var(--pgn-color-gray-500); color: $gray-500;
padding: 6rem 0; padding: 6rem 0;
text-align: center; text-align: center;
} }
@@ -76,17 +76,17 @@
&:hover { &:hover {
text-decoration: none; text-decoration: none;
background: var(--pgn-color-light-300); background: $light-300;
} }
&:not(:first-child) { &:not(:first-child) {
border-top: 1px solid var(--pgn-color-light-300); border-top: 1px solid $light-300;
} }
} }
&__icon { &__icon {
padding: 0.375rem 0 0 0.375rem; padding: 0.375rem 0 0 0.375rem;
color: var(--pgn-color-gray-300); color: $gray-300;
} }
&__info { &__info {
@@ -99,7 +99,7 @@
align-items: center; align-items: center;
line-height: 2.5; line-height: 2.5;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--pgn-color-black); color: $black;
> span { > span {
display: block; display: block;
@@ -113,7 +113,7 @@
font-variant-numeric: lining-nums tabular-nums; font-variant-numeric: lining-nums tabular-nums;
min-width: 1.25rem; min-width: 1.25rem;
line-height: 1rem; line-height: 1rem;
background: var(--pgn-color-light-300); background: $light-300;
border-radius: 99rem; border-radius: 99rem;
font-style: normal; font-style: normal;
margin-left: 0.375rem; margin-left: 0.375rem;
@@ -125,7 +125,7 @@
&__breadcrumbs { &__breadcrumbs {
display: flex; display: flex;
gap: 1.25rem; gap: 1.25rem;
color: var(--pgn-color-gray-500); color: $gray-500;
overflow: hidden; overflow: hidden;
list-style: none; list-style: none;
padding: 0; padding: 0;
@@ -156,14 +156,14 @@
} }
.courseware-search-results-tabs { .courseware-search-results-tabs {
border-bottom-color: var(--pgn-color-gray-400) !important; border-bottom-color: $gray-400 !important;
&.nav-tabs .nav-link.active { &.nav-tabs .nav-link.active {
border-bottom-width: 4px !important; border-bottom-width: 4px !important;
} }
} }
@media (--pgn-size-breakpoint-min-width-md) { @media (min-width: map-get($grid-breakpoints, 'md')) {
.courseware-search { .courseware-search {
&__close { &__close {
right: -2.5rem; right: -2.5rem;

View File

@@ -10,8 +10,8 @@ describe('mapSearchResponse', () => {
response = mapSearchResponse(camelCaseObject(mockedResponse)); response = mapSearchResponse(camelCaseObject(mockedResponse));
}); });
it('should match number of results', () => { it('should match snapshot', () => {
expect(response.results.length).toBe(mockedResponse.results.length); expect(response).toMatchSnapshot();
}); });
it('should match expected filters', () => { it('should match expected filters', () => {
@@ -24,25 +24,6 @@ describe('mapSearchResponse', () => {
]; ];
expect(response.filters).toEqual(expectedFilters); expect(response.filters).toEqual(expectedFilters);
}); });
it('should match expected results', () => {
const mockFirstResult = mockedResponse.results[0];
const expectedFirstResult = {
id: mockFirstResult.data.id,
title: mockFirstResult.data.content.display_name,
type: mockFirstResult.data.content_type.toLowerCase(),
location: mockFirstResult.data.location,
url: mockFirstResult.data.url,
contentHits: 0,
score: mockFirstResult.score,
};
expect(response.results[0]).toEqual(expectedFirstResult);
});
it('should match expected ms and max score', () => {
expect(response.maxScore).toBe(mockedResponse.max_score);
expect(response.ms).toBe(mockedResponse.took);
});
}); });
describe('when the a keyword is provided', () => { describe('when the a keyword is provided', () => {

View File

@@ -0,0 +1,941 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"showSearch": false,
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"dates": {
"course-v1:edX+DemoX+Demo_Course": {
"courseDateBlocks": [
{
"date": "2020-05-01T17:59:41Z",
"dateType": "course-start-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "",
"title": "Course Starts",
},
{
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-04T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Multi Badges Completed",
},
{
"assignmentType": "Homework",
"date": "2020-05-05T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Multi Badges Past Due",
},
{
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Past Due 1",
},
{
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Past Due 2",
},
{
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-28T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Completed/Due 1",
},
{
"assignmentType": "Homework",
"date": "2020-05-28T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Completed/Due 2",
},
{
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Completed 1",
},
{
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Completed 2",
},
{
"date": "2020-06-16T17:59:40.942669Z",
"dateType": "verified-upgrade-deadline",
"description": "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Upgrade to Verified Certificate",
},
{
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "One Verified 1",
},
{
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Verified 2",
},
{
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": "ORA Dates are set by the instructor, and can't be changed",
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "ORA Verified 2",
},
{
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Both Verified 1",
},
{
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Both Verified 2",
},
{
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"learnerHasAccess": true,
"title": "One Unreleased 1",
},
{
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Unreleased 2",
},
{
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Both Unreleased 1",
},
{
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Both Unreleased 2",
},
{
"date": "2030-08-23T00:00:00Z",
"dateType": "course-end-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "",
"title": "Course Ends",
},
{
"date": "2030-09-01T00:00:00Z",
"dateType": "verification-deadline-date",
"description": "You must successfully complete verification before this date to qualify for a Verified Certificate.",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Verification Deadline",
},
],
"datesBannerInfo": {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
"verifiedUpgradeLink": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
"hasEnded": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"learnerIsFullAccess": true,
},
},
},
"plugins": {},
"recommendations": {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;
exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"showSearch": false,
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"outline": {
"course-v1:edX+DemoX+Demo_Course": {
"accessExpiration": null,
"certData": {
"certStatus": null,
"certWebViewUrl": null,
"certificateAvailableDate": null,
},
"courseBlocks": {
"courses": {
"block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": {
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionIds": [
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
],
"title": "bcdabcdabcdabcdabcdabcdabcdabcd3",
},
},
"sections": {
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2": {
"complete": false,
"courseId": "course-v1:edX+DemoX+Demo_Course",
"hideFromTOC": undefined,
"id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"resumeBlock": false,
"sequenceIds": [
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
],
"title": "Title of Section",
},
},
"sequences": {
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1": {
"complete": false,
"description": null,
"due": null,
"effortActivities": 2,
"effortTime": 15,
"hideFromTOC": undefined,
"icon": null,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
"navigationDisabled": undefined,
"sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"showLink": true,
"title": "Title of Sequence",
},
},
},
"courseGoals": {
"daysPerWeek": null,
"goalOptions": [],
"selectedGoal": null,
"subscribedToReminders": null,
"weeklyLearningGoalEnabled": false,
},
"courseTools": [
{
"analyticsId": "edx.bookmarks",
"title": "Bookmarks",
"url": "https://example.com/bookmarks",
},
],
"datesBannerInfo": {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
},
"datesWidget": {
"courseDateBlocks": [],
},
"enableProctoredExams": undefined,
"enrollAlert": {
"canEnroll": true,
"extraText": "Contact the administrator.",
},
"enrollmentMode": undefined,
"handoutsHtml": "<ul><li>Handout 1</li></ul>",
"hasEnded": undefined,
"hasScheduledContent": null,
"id": "course-v1:edX+DemoX+Demo_Course",
"offer": null,
"resumeCourse": {
"hasVisitedCourse": false,
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+Test+Block@12345abcde",
},
"timeOffsetMillis": 0,
"userHasPassingGrade": undefined,
"verifiedMode": {
"accessExpirationDate": "2050-01-01T12:00:00",
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "ABCD1234",
"upgradeUrl": "http://localhost:18000/dashboard",
},
"welcomeMessageHtml": "<p>Welcome to this course!</p>",
},
},
},
"plugins": {},
"recommendations": {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;
exports[`Data layer integration tests Test fetchProgressTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"showSearch": false,
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"progress": {
"course-v1:edX+DemoX+Demo_Course": {
"accessExpiration": null,
"certificateData": {},
"completionSummary": {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"courseGrade": {
"isPassing": true,
"letterGrade": "pass",
"percent": 1,
},
"courseId": "course-v1:edX+DemoX+Demo_Course",
"creditCourseRequirements": null,
"end": "3027-03-31T00:00:00Z",
"enrollmentMode": "audit",
"gradesFeatureIsFullyLocked": false,
"gradesFeatureIsPartiallyLocked": false,
"gradingPolicy": {
"assignmentPolicies": [
{
"averageGrade": "1.0000",
"numDroppable": 1,
"shortLabel": "HW",
"type": "Homework",
"weight": 1,
"weightedGrade": 1,
},
],
"gradeRange": {
"pass": 0.75,
},
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionScores": [
{
"displayName": "First section",
"subsections": [
{
"assignmentType": "Homework",
"blockKey": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345",
"displayName": "First subsection",
"hasGradedAssignment": true,
"learnerHasAccess": true,
"numPointsEarned": 0,
"numPointsPossible": 3,
"percentGraded": 0,
"problemScores": [
{
"earned": 0,
"possible": 1,
},
{
"earned": 0,
"possible": 1,
},
{
"earned": 0,
"possible": 1,
},
],
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection",
},
],
},
{
"displayName": "Second section",
"subsections": [
{
"assignmentType": "Homework",
"displayName": "Second subsection",
"hasGradedAssignment": true,
"numPointsEarned": 1,
"numPointsPossible": 1,
"percentGraded": 1,
"problemScores": [
{
"earned": 1,
"possible": 1,
},
],
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection",
},
],
},
],
"studioUrl": "http://studio.edx.org/settings/grading/course-v1:edX+Test+run",
"userHasPassingGrade": false,
"verificationData": {
"link": null,
"status": "none",
"statusDate": null,
},
"verifiedMode": null,
},
},
},
"plugins": {},
"recommendations": {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;

View File

@@ -90,14 +90,14 @@ describe('Data layer integration tests', () => {
const state = store.getState(); const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded'); expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toEqual(expect.objectContaining({ expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID // The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This UUID is generated on each run. // to keep track of conversations. This causes snapshots to fail, because this UUID
// Instead, we use an asymmetric matcher here. // is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({ learningAssistant: expect.objectContaining({
conversationId: expect.any(String), conversationId: expect.any(String),
}), }),
})); });
}); });
it.each([401, 403, 404])( it.each([401, 403, 404])(
@@ -137,14 +137,14 @@ describe('Data layer integration tests', () => {
const state = store.getState(); const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded'); expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toEqual(expect.objectContaining({ expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID // The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This UUID is generated on each run. // to keep track of conversations. This causes snapshots to fail, because this UUID
// Instead, we use an asymmetric matcher here. // is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({ learningAssistant: expect.objectContaining({
conversationId: expect.any(String), conversationId: expect.any(String),
}), }),
})); });
}); });
it.each([401, 403, 404])( it.each([401, 403, 404])(
@@ -185,14 +185,14 @@ describe('Data layer integration tests', () => {
const state = store.getState(); const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded'); expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toEqual(expect.objectContaining({ expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID // The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This UUID is generated on each run. // to keep track of conversations. This causes snapshots to fail, because this UUID
// Instead, we use an asymmetric matcher here. // is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({ learningAssistant: expect.objectContaining({
conversationId: expect.any(String), conversationId: expect.any(String),
}), }),
})); });
}); });
it('Should handle the url including a targetUserId', async () => { it('Should handle the url including a targetUserId', async () => {

View File

@@ -65,7 +65,6 @@ const DateSummary = ({
)} )}
{!linkedTitle && dateBlock.link && ( {!linkedTitle && dateBlock.link && (
<a <a
id={dateBlock.dateType === 'verified-upgrade-deadline' ? 'date-verified-upgrade-deadline' : ''}
href={dateBlock.link} href={dateBlock.link}
onClick={dateBlock.dateType === 'verified-upgrade-deadline' ? logVerifiedUpgradeClick : () => {}} onClick={dateBlock.dateType === 'verified-upgrade-deadline' ? logVerifiedUpgradeClick : () => {}}
className="description-link" className="description-link"

View File

@@ -39,7 +39,7 @@ const CourseDates = () => {
/> />
))} ))}
</ol> </ol>
<a id="dates-tab-link" className="font-weight-bold ml-4 pl-1 small" href={datesTabLink}> <a className="font-weight-bold ml-4 pl-1 small" href={datesTabLink}>
{intl.formatMessage(messages.allDates)} {intl.formatMessage(messages.allDates)}
</a> </a>
</div> </div>

View File

@@ -1,18 +1,22 @@
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
.flag-button { .flag-button {
background-color: var(--pgn-color-white); background-color: $white;
border: 1px solid var(--pgn-color-light-400); border: 1px solid $light-400;
border-radius: .2rem; border-radius: .2rem;
box-shadow: 0 0 0 2px var(--pgn-color-light-400); box-shadow: 0 0 0 2px $light-400;
&:hover { &:hover {
border: 1px solid var(--pgn-color-primary-300); border: 1px solid $primary-300;
box-shadow: 0 0 0 2px var(--pgn-color-white); box-shadow: 0 0 0 2px $white;
} }
} }
.flag-button-selected { .flag-button-selected {
border: 1px solid var(--pgn-color-primary-300); border: 1px solid $primary-300;
box-shadow: 0 0 0 2px var(--pgn-color-primary-300); box-shadow: 0 0 0 2px $primary-300;
pointer-events: none; pointer-events: none;
} }

View File

@@ -3,7 +3,6 @@ import { useDispatch, useSelector } from 'react-redux';
import camelCase from 'lodash.camelcase'; import camelCase from 'lodash.camelcase';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { getExternalLinkUrl } from '@edx/frontend-platform';
import { Button } from '@openedx/paragon'; import { Button } from '@openedx/paragon';
import messages from '../messages'; import messages from '../messages';
@@ -208,7 +207,7 @@ const ProctoringInfoPanel = () => {
{isSubmissionRequired(readableStatus) && ( {isSubmissionRequired(readableStatus) && (
onboardingExamButton onboardingExamButton
)} )}
<Button variant="outline-primary" block href={getExternalLinkUrl('https://support.edx.org/hc/en-us/sections/115004169247-Taking-Timed-and-Proctored-Exams')}> <Button variant="outline-primary" block href="https://support.edx.org/hc/en-us/sections/115004169247-Taking-Timed-and-Proctored-Exams">
{intl.formatMessage(messages.proctoringReviewRequirementsButton)} {intl.formatMessage(messages.proctoringReviewRequirementsButton)}
</Button> </Button>
</div> </div>

View File

@@ -1,10 +1,10 @@
.outline-sidebar-proctoring-panel { .outline-sidebar-proctoring-panel {
border: 1px solid var(--pgn-color-dark-500); border: 1px solid $dark-500;
border-top: 5px solid var(--pgn-color-brand-600); border-top: 5px solid $brand-600;
} }
.proctoring-onboarding-success { .proctoring-onboarding-success {
border-top: 5px solid var(--pgn-color-primary-500); border-top: 5px solid $primary-500;
} }
.proctoring-onboarding-submitted { .proctoring-onboarding-submitted {
border-top: 5px solid var(--pgn-color-dark-500); border-top: 5px solid $dark-500;
} }

View File

@@ -187,8 +187,7 @@ const CertificateStatus = () => {
// regardless of passing or nonpassing status // regardless of passing or nonpassing status
if (!canViewCertificate) { if (!canViewCertificate) {
certCase = 'notAvailable'; certCase = 'notAvailable';
// use the certificate_available_date if it is available, otherwise use the end date of the course endDate = intl.formatDate(end, {
endDate = intl.formatDate((certificateAvailableDate || end), {
year: 'numeric', year: 'numeric',
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',

View File

@@ -7,18 +7,18 @@
.donut-chart-label { .donut-chart-label {
font: { font: {
family: var(--pgn-typography-font-family-sans-serif); family: $font-family-sans-serif;
size: .2rem; size: .2rem;
weight: var(--pgn-typography-font-weight-normal); weight: $font-weight-normal;
} }
text-anchor: middle; text-anchor: middle;
} }
.donut-chart-number { .donut-chart-number {
font: { font: {
family: var(--pgn-typography-font-family-monospace); family: $font-family-monospace;
size: .5rem; size: .5rem;
weight: var(--pgn-typography-font-weight-bold); weight: $font-weight-bold;
} }
line-height: 1rem; line-height: 1rem;
text-anchor: middle; text-anchor: middle;
@@ -29,7 +29,7 @@
} }
.donut-chart-text { .donut-chart-text {
fill: var(--pgn-color-primary-500); fill: $primary-500;
-moz-transform: translateY(0.25em); -moz-transform: translateY(0.25em);
-ms-transform: translateY(0.25em); -ms-transform: translateY(0.25em);
-webkit-transform: translateY(0.25em); -webkit-transform: translateY(0.25em);
@@ -56,7 +56,7 @@
.donut-ring, .donut-segment, .donut-hole { .donut-ring, .donut-segment, .donut-hole {
&.complete-stroke { &.complete-stroke {
stroke: var(--pgn-color-info-500); stroke: $info-500;
} }
&.divider-stroke { &.divider-stroke {
@@ -65,10 +65,10 @@
} }
&.incomplete-stroke { &.incomplete-stroke {
stroke: var(--pgn-color-light-300); stroke: $light-300;
} }
&.locked-stroke { &.locked-stroke {
stroke: var(--pgn-color-primary-500); stroke: $primary-500;
} }
} }

View File

@@ -48,7 +48,7 @@ const CourseGradeHeader = () => {
previewText = intl.formatMessage(messages.courseGradePreviewUpgradeDeadlinePassedBody); previewText = intl.formatMessage(messages.courseGradePreviewUpgradeDeadlinePassedBody);
} }
return ( return (
<div id="grade-course-header" className="row w-100 m-0 p-4 rounded-top bg-primary-500 text-white"> <div className="row w-100 m-0 p-4 rounded-top bg-primary-500 text-white">
<div className={`col-12 ${verifiedMode ? 'col-md-9' : ''} p-0`}> <div className={`col-12 ${verifiedMode ? 'col-md-9' : ''} p-0`}>
<div className="row w-100 m-0 p-0"> <div className="row w-100 m-0 p-0">
<div className="col-1 p-0"> <div className="col-1 p-0">
@@ -71,7 +71,7 @@ const CourseGradeHeader = () => {
</div> </div>
{verifiedMode && ( {verifiedMode && (
<div className="col-12 col-md-3 mt-3 mt-md-0 p-0 align-self-center text-right"> <div className="col-12 col-md-3 mt-3 mt-md-0 p-0 align-self-center text-right">
<Button id="upgrade-button" variant="brand" size="sm" href={verifiedMode.upgradeUrl} onClick={logUpgradeButtonClick}> <Button variant="brand" size="sm" href={verifiedMode.upgradeUrl} onClick={logUpgradeButtonClick}>
{intl.formatMessage(messages.courseGradePreviewUpgradeButton)} {intl.formatMessage(messages.courseGradePreviewUpgradeButton)}
</Button> </Button>
</div> </div>

View File

@@ -4,24 +4,24 @@
} }
.grade-bar__base { .grade-bar__base {
fill: var(--pgn-color-light-300); fill: $light-300;
} }
.grade-bar__divider { .grade-bar__divider {
fill: var(--pgn-color-primary-500); fill: $primary-500;
width: 1px; width: 1px;
} }
.grade-bar--passing { .grade-bar--passing {
fill: var(--pgn-color-primary-500); fill: $primary-500;
} }
.grade-bar--current-passing { .grade-bar--current-passing {
fill: var(--pgn-color-success-500); fill: $success-500;
} }
.grade-bar--current-non-passing { .grade-bar--current-non-passing {
fill: var(--pgn-color-accent-b); fill: $accent-b;
} }
} }
@@ -31,22 +31,22 @@
#minimum-grade-tooltip { #minimum-grade-tooltip {
.arrow::after { .arrow::after {
border-bottom-color: var(--pgn-color-primary-500); border-bottom-color: $primary-500;
} }
} }
#passing-grade-tooltip { #passing-grade-tooltip {
background: var(--pgn-color-success-500); background: $success-500;
.arrow::after { .arrow::after {
border-top-color: var(--pgn-color-success-500); border-top-color: $success-500;
} }
} }
#non-passing-grade-tooltip { #non-passing-grade-tooltip {
background: var(--pgn-color-accent-b); background: $accent-b;
.arrow::after { .arrow::after {
border-top-color: var(--pgn-color-accent-b); border-top-color: $accent-b;
} }
} }

View File

@@ -34,7 +34,7 @@ const UpgradeToCompleteAlert = ({ logUpgradeLinkClick }) => {
} }
return ( return (
<Alert id="upgrade-complete-alert" className="bg-light-200"> <Alert className="bg-light-200">
<Row className="w-100 m-0"> <Row className="w-100 m-0">
<Col xs={12} md={9} className="small p-0 pr-md-2"> <Col xs={12} md={9} className="small p-0 pr-md-2">
<Alert.Heading>{intl.formatMessage(messages.upgradeToCompleteHeader)}</Alert.Heading> <Alert.Heading>{intl.formatMessage(messages.upgradeToCompleteHeader)}</Alert.Heading>

View File

@@ -36,7 +36,7 @@ const UpgradeToShiftDatesAlert = ({ logUpgradeLinkClick, model }) => {
} }
return ( return (
<Alert id="upgrade-shift-dates-alert" className="bg-light-200"> <Alert className="bg-light-200">
<Row className="w-100 m-0"> <Row className="w-100 m-0">
<Col xs={12} md={9} className="small p-0 pr-md-2"> <Col xs={12} md={9} className="small p-0 pr-md-2">
<strong>{intl.formatMessage(messages.missedDeadlines)}</strong> <strong>{intl.formatMessage(messages.missedDeadlines)}</strong>

View File

@@ -5,13 +5,13 @@
.nav a, .nav a,
.nav button { .nav button {
&:hover { &:hover {
background-color: var(--pgn-color-light-400); background-color: $light-400;
} }
} }
.nav a { .nav a {
&:not(.active):hover { &:not(.active):hover {
background-color: var(--pgn-color-light-400); background-color: $light-400;
border-bottom: none; border-bottom: none;
} }
} }

View File

@@ -1,7 +1,7 @@
import { getConfig, history } from '@edx/frontend-platform'; import { getConfig, history } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react'; import { AppProvider } from '@edx/frontend-platform/react';
import { waitForElementToBeRemoved } from '@testing-library/dom'; import { waitForElementToBeRemoved, fireEvent } from '@testing-library/dom';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
@@ -193,13 +193,15 @@ describe('CoursewareContainer', () => {
expect(courseHeader.querySelector('.course-title')).toHaveTextContent(courseHomeMetadata.title); expect(courseHeader.querySelector('.course-title')).toHaveTextContent(courseHomeMetadata.title);
} }
function assertNoSequenceNavigation(container) { function assertSequenceNavigation(container, expectedUnitCount = 3) {
// Ensure we had appropriate sequence navigation buttons. We should only have one unit.
const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation a, nav.sequence-navigation button'); const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation a, nav.sequence-navigation button');
expect(sequenceNavButtons).toHaveLength(0); expect(sequenceNavButtons).toHaveLength(expectedUnitCount + 2);
expect(container.querySelector('button, a')).not.toHaveTextContent('Previous'); expect(sequenceNavButtons[0]).toHaveTextContent('Previous');
expect(container.querySelector('svg.fa-tasks')).toBeNull(); // Prove this button is rendering an SVG tasks icon, meaning it's a unit/vertical.
expect(container.querySelector('button, a')).not.toHaveTextContent('Next'); expect(sequenceNavButtons[1].querySelector('svg')).toHaveClass('fa-tasks');
expect(sequenceNavButtons[sequenceNavButtons.length - 1]).toHaveTextContent('Next');
} }
beforeEach(async () => { beforeEach(async () => {
@@ -222,7 +224,7 @@ describe('CoursewareContainer', () => {
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container);
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents'); expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId); expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
@@ -245,7 +247,7 @@ describe('CoursewareContainer', () => {
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container);
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents'); expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId); expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
@@ -272,12 +274,29 @@ describe('CoursewareContainer', () => {
setUpMockRequests({ courseBlocks }); setUpMockRequests({ courseBlocks });
}); });
// describe('when the URL contains a unit ID', () => {
// it('should ignore the section ID and redirect based on the unit ID', async () => {
// const urlUnit = unitTree[1][1][1];
// setUrl(sectionTree[1].id, urlUnit.id);
// const container = await loadContainer();
// assertLoadedHeader(container);
// assertSequenceNavigation(container, 2);
// assertLocation(container, sequenceTree[1][1].id, urlUnit.id);
// });
// it('should ignore invalid unit IDs and redirect to the course root', async () => {
// setUrl(sectionTree[1].id, 'foobar');
// await loadContainer();
// expect(global.location.href).toEqual(`http://localhost/course/${courseId}`);
// });
// });
describe('when the URL does not contain a unit ID', () => { describe('when the URL does not contain a unit ID', () => {
it('should choose a unit within the section\'s first sequence', async () => { it('should choose a unit within the section\'s first sequence', async () => {
setUrl(sectionTree[1].id); setUrl(sectionTree[1].id);
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container, 2);
assertLocation(container, sequenceTree[1][0].id, unitTree[1][0][0].id); assertLocation(container, sequenceTree[1][0].id, unitTree[1][0][0].id);
}); });
}); });
@@ -323,6 +342,27 @@ describe('CoursewareContainer', () => {
}); });
}); });
// describe('when the URL only contains a unit ID', () => {
// const { courseBlocks, unitTree, sequenceTree } = buildBinaryCourseBlocks(courseId, courseMetadata.name);
// beforeEach(async () => {
// setUpMockRequests({ courseBlocks });
// });
// it('should insert the sequence ID into the URL', async () => {
// const unit = unitTree[1][0][1];
// history.push(`/course/${courseId}/${unit.id}`);
// const container = await loadContainer();
// assertLoadedHeader(container);
// assertSequenceNavigation(container, 2);
// const expectedSequenceId = sequenceTree[1][0].id;
// const expectedUrl = `http://localhost/course/${courseId}/${expectedSequenceId}/${unit.id}`;
// expect(global.location.href).toEqual(expectedUrl);
// expect(container.querySelector('.fake-unit')).toHaveTextContent(unit.id);
// });
// });
describe('when the URL contains a course ID and sequence ID', () => { describe('when the URL contains a course ID and sequence ID', () => {
const sequenceBlock = defaultSequenceBlock; const sequenceBlock = defaultSequenceBlock;
const unitBlocks = defaultUnitBlocks; const unitBlocks = defaultUnitBlocks;
@@ -332,7 +372,7 @@ describe('CoursewareContainer', () => {
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container);
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents'); expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId); expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
@@ -351,7 +391,7 @@ describe('CoursewareContainer', () => {
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container);
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents'); expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId); expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
@@ -368,24 +408,44 @@ describe('CoursewareContainer', () => {
const container = await loadContainer(); const container = await loadContainer();
assertLoadedHeader(container); assertLoadedHeader(container);
assertNoSequenceNavigation(container); assertSequenceNavigation(container);
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents'); expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId); expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[2].id); expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[2].id);
}); });
it('should render the sequence_navigation plugin slot correctly', async () => { it('should navigate between units and check block completion', async () => {
axiosMock axiosMock.onPost(`${courseId}/xblock/${sequenceBlock.id}/handler/get_completion`).reply(200, {
.onPost(`${courseId}/xblock/${sequenceBlock.id}/handler/get_completion`) complete: true,
.reply(200, { complete: true }); });
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[0].id}`); history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[0].id}`);
await loadContainer(); const container = await loadContainer();
expect(screen.getByTestId('org.openedx.frontend.learning.sequence_navigation.v1')).toBeInTheDocument(); const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation a, nav.sequence-navigation button');
const sequenceNextButton = sequenceNavButtons[4];
expect(sequenceNextButton).toHaveTextContent('Next');
fireEvent.click(sequenceNextButton);
expect(global.location.href).toEqual(`http://localhost/course/${courseId}/${sequenceBlock.id}/${unitBlocks[1].id}`);
}); });
}); });
// describe('when the current sequence is an exam', () => {
// const { location } = window;
// beforeEach(() => {
// delete window.location;
// window.location = {
// assign: jest.fn(),
// };
// });
// afterEach(() => {
// window.location = location;
// });
// });
}); });
describe('when receiving a course_access error_code', () => { describe('when receiving a course_access error_code', () => {

View File

@@ -1,20 +1,18 @@
import PropTypes from 'prop-types';
import { import {
generatePath, useParams, useLocation, useSearchParams, generatePath, useParams, useLocation,
} from 'react-router-dom'; } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import queryString from 'query-string';
import { REDIRECT_MODES } from '../constants'; import { REDIRECT_MODES } from '../constants';
interface Props { const RedirectPage = ({
pattern: string; pattern, mode,
mode: string; }) => {
}
const RedirectPage = ({ pattern = '', mode }: Props) => {
const { courseId } = useParams(); const { courseId } = useParams();
const location = useLocation(); const location = useLocation();
const [searchParams] = useSearchParams(); const { consentPath } = queryString.parse(location?.search);
const consentPath = searchParams.get('consentPath') ?? '';
const { const {
LMS_BASE_URL, LMS_BASE_URL,
@@ -41,4 +39,13 @@ const RedirectPage = ({ pattern = '', mode }: Props) => {
return null; return null;
}; };
RedirectPage.propTypes = {
pattern: PropTypes.string,
mode: PropTypes.string.isRequired,
};
RedirectPage.defaultProps = {
pattern: null,
};
export default RedirectPage; export default RedirectPage;

View File

@@ -16,7 +16,6 @@ jest.mock('react-router-dom', () => ({
useLocation: () => ({ useLocation: () => ({
search: '?consentPath=/some-path', search: '?consentPath=/some-path',
}), }),
useSearchParams: () => [new URLSearchParams('?consentPath=/some-path'), () => {}],
})); }));
describe('RedirectPage component', () => { describe('RedirectPage component', () => {

View File

@@ -1,13 +1,14 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { breakpoints, useWindowSize } from '@openedx/paragon'; import { breakpoints, useWindowSize } from '@openedx/paragon';
import { AlertList } from '@src/generic/user-messages'; import { AlertList } from '@src/generic/user-messages';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import { getCoursewareOutlineSidebarSettings } from '../data/selectors';
import Chat from './chat/Chat'; import Chat from './chat/Chat';
import SidebarProvider from './sidebar/SidebarContextProvider'; import SidebarProvider from './sidebar/SidebarContextProvider';
import NewSidebarProvider from './new-sidebar/SidebarContextProvider'; import NewSidebarProvider from './new-sidebar/SidebarContextProvider';
@@ -36,6 +37,8 @@ const Course = ({
} = useModel('courseHomeMeta', courseId); } = useModel('courseHomeMeta', courseId);
const sequence = useModel('sequences', sequenceId); const sequence = useModel('sequences', sequenceId);
const section = useModel('sections', sequence ? sequence.sectionId : null); const section = useModel('sections', sequence ? sequence.sectionId : null);
const { enableNavigationSidebar } = useSelector(getCoursewareOutlineSidebarSettings);
const navigationDisabled = enableNavigationSidebar || (sequence?.navigationDisabled ?? false);
const navigate = useNavigate(); const navigate = useNavigate();
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -81,13 +84,17 @@ const Course = ({
<title>{`${pageTitleBreadCrumbs.join(' | ')} | ${getConfig().SITE_NAME}`}</title> <title>{`${pageTitleBreadCrumbs.join(' | ')} | ${getConfig().SITE_NAME}`}</title>
</Helmet> </Helmet>
<div className="position-relative d-flex align-items-xl-center mb-4 mt-1 flex-column flex-xl-row"> <div className="position-relative d-flex align-items-xl-center mb-4 mt-1 flex-column flex-xl-row">
<CourseBreadcrumbsSlot {navigationDisabled || (
courseId={courseId} <>
sectionId={section ? section.id : null} <CourseBreadcrumbsSlot
sequenceId={sequenceId} courseId={courseId}
isStaff={isStaff} sectionId={section ? section.id : null}
unitId={unitId} sequenceId={sequenceId}
/> isStaff={isStaff}
unitId={unitId}
/>
</>
)}
{shouldDisplayChat && ( {shouldDisplayChat && (
<> <>
<Chat <Chat

View File

@@ -202,7 +202,7 @@ describe('Course', () => {
}); });
}); });
it('doesn\'t renders course breadcrumbs by default', async () => { it('renders course breadcrumbs as expected', async () => {
const courseMetadata = Factory.build('courseMetadata'); const courseMetadata = Factory.build('courseMetadata');
const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build( const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build(
'block', 'block',
@@ -210,7 +210,7 @@ describe('Course', () => {
{ courseId: courseMetadata.id }, { courseId: courseMetadata.id },
)); ));
const testStore = await initializeTestStore({ const testStore = await initializeTestStore({
courseMetadata, unitBlocks, courseMetadata, unitBlocks, enableNavigationSidebar: { enable_navigation_sidebar: false },
}, false); }, false);
const { courseware, models } = testStore.getState(); const { courseware, models } = testStore.getState();
const { courseId, sequenceId } = courseware; const { courseId, sequenceId } = courseware;
@@ -226,10 +226,10 @@ describe('Course', () => {
await waitFor(() => { await waitFor(() => {
expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument(); expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument();
}); });
// expect the section and sequence "titles" not to be loaded in as breadcrumb labels. // expect the section and sequence "titles" to be loaded in as breadcrumb labels.
await waitFor(() => { waitFor(() => {
expect(screen.queryByText(Object.values(models.sections)[0].title)).not.toBeInTheDocument(); expect(screen.findByText(Object.values(models.sections)[0].title)).toBeInTheDocument();
expect(screen.queryByText(Object.values(models.sequences)[0].title)).not.toBeInTheDocument(); expect(screen.findByText(Object.values(models.sequences)[0].title)).toBeInTheDocument();
}); });
}); });

View File

@@ -22,6 +22,7 @@
justify-content: center; justify-content: center;
button { button {
@extend .btn-primary;
font-size: 1.2rem; font-size: 1.2rem;
width: 50%; width: 50%;
} }

View File

@@ -149,7 +149,7 @@ const Calculator = () => {
/> />
</li> </li>
</ul> </ul>
<table className="pgn__data-table small"> <table className="table small">
<thead> <thead>
<tr> <tr>
<th scope="col"> <th scope="col">

View File

@@ -4,19 +4,4 @@
background-color: #f1f1f1; background-color: #f1f1f1;
box-shadow: 0 -1px 0 0 #ddd; box-shadow: 0 -1px 0 0 #ddd;
} }
table {
tr {
border-bottom: var(--pgn-size-border-width) solid var(--pgn-color-border);
}
thead tr {
border-bottom: calc(2 * var(--pgn-size-border-width)) solid var(--pgn-color-border);
border-top: var(--pgn-size-border-width) solid var(--pgn-color-border);
}
tbody tr {
vertical-align: top;
}
}
} }

View File

@@ -8,8 +8,8 @@
display: inline-block; display: inline-block;
position: relative; position: relative;
z-index: 2; z-index: 2;
background-color: #f1f1f1 !important; background-color: #f1f1f1;
border: solid 1px #ddd !important; border: solid 1px #ddd;
border-bottom: none; border-bottom: none;
border-top-left-radius: .3rem; border-top-left-radius: .3rem;
border-top-right-radius: .3rem; border-top-right-radius: .3rem;

View File

@@ -25,12 +25,12 @@ import messages from './messages';
import { useModel } from '../../../generic/model-store'; import { useModel } from '../../../generic/model-store';
import { requestCert } from '../../../course-home/data/thunks'; import { requestCert } from '../../../course-home/data/thunks';
import ProgramCompletion from './ProgramCompletion'; import ProgramCompletion from './ProgramCompletion';
import DashboardFootnote from './DashboardFootnote';
import UpgradeFootnote from './UpgradeFootnote'; import UpgradeFootnote from './UpgradeFootnote';
import SocialIcons from '../../social-share/SocialIcons'; import SocialIcons from '../../social-share/SocialIcons';
import { logClick, logVisit } from './utils'; import { logClick, logVisit } from './utils';
import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links'; import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links';
import DashboardFootnote from './DashboardFootnote'; import CourseRecommendationsSlot from '../../../plugin-slots/CourseRecommendationsSlot';
import { CourseRecommendationsSlot } from '../../../plugin-slots/CourseExitPluginSlots';
const LINKEDIN_BLUE = '#2867B2'; const LINKEDIN_BLUE = '#2867B2';

View File

@@ -1,5 +1,8 @@
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
@@ -7,12 +10,13 @@ import CourseCelebration from './CourseCelebration';
import CourseInProgress from './CourseInProgress'; import CourseInProgress from './CourseInProgress';
import CourseNonPassing from './CourseNonPassing'; import CourseNonPassing from './CourseNonPassing';
import { COURSE_EXIT_MODES, getCourseExitMode } from './utils'; import { COURSE_EXIT_MODES, getCourseExitMode } from './utils';
import messages from './messages';
import { unsubscribeFromGoalReminders } from './data/thunks'; import { unsubscribeFromGoalReminders } from './data/thunks';
import { CourseExitViewCoursesPluginSlot } from '../../../plugin-slots/CourseExitPluginSlots';
import { useModel } from '../../../generic/model-store'; import { useModel } from '../../../generic/model-store';
const CourseExit = () => { const CourseExit = () => {
const intl = useIntl();
const { courseId } = useSelector(state => state.courseware); const { courseId } = useSelector(state => state.courseware);
const { const {
certificateData, certificateData,
@@ -60,7 +64,14 @@ const CourseExit = () => {
return ( return (
<> <>
<CourseExitViewCoursesPluginSlot /> <div className="row w-100 mt-2 mb-4 justify-content-end">
<Button
variant="outline-primary"
href={`${getConfig().LMS_BASE_URL}/dashboard`}
>
{intl.formatMessage(messages.viewCoursesButton)}
</Button>
</div>
{body} {body}
</> </>
); );

View File

@@ -1,19 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useSelector } from 'react-redux';
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@openedx/paragon';
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
import { getConfig } from '@edx/frontend-platform';
import { useModel } from '../../../generic/model-store';
import { DashboardFootnoteLinkPluginSlot } from '../../../plugin-slots/CourseExitPluginSlots';
import Footnote from './Footnote'; import Footnote from './Footnote';
import messages from './messages'; import messages from './messages';
import { logClick } from './utils';
const DashboardFootnote = ({ variant }) => { const DashboardFootnote = ({ variant }) => {
const intl = useIntl(); const intl = useIntl();
const dashboardLink = (<DashboardFootnoteLinkPluginSlot variant={variant} />); const { courseId } = useSelector(state => state.courseware);
const { org } = useModel('courseHomeMeta', courseId);
const { administrator } = getAuthenticatedUser();
const dashboardLink = (
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={`${getConfig().LMS_BASE_URL}/dashboard`}
className="text-reset"
onClick={() => logClick(org, courseId, administrator, 'dashboard_footnote', { variant })}
>
{intl.formatMessage(messages.dashboardLink)}
</Hyperlink>
);
return ( return (
<Footnote <Footnote
icon={faCalendarAlt} icon={faCalendarAlt}
text={intl.formatMessage(messages.dashboardInfo, { dashboardLink })} text={(
<FormattedMessage
id="courseCelebration.dashboardInfo" // for historical reasons
defaultMessage="You can access this course and its materials on your {dashboardLink}."
description="Text that precedes link to learner's dashboard"
values={{ dashboardLink }}
/>
)}
/> />
); );
}; };

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const Footnote = ({ icon, text }) => ( const Footnote = ({ icon, text }) => (
<div id="celebration-footnote-wrapper" className="row w-100 mx-0 my-4 justify-content-center"> <div className="row w-100 mx-0 my-4 justify-content-center">
<p className="text-gray-700"> <p className="text-gray-700">
<FontAwesomeIcon icon={icon} style={{ width: '20px' }} />&nbsp; <FontAwesomeIcon icon={icon} style={{ width: '20px' }} />&nbsp;
{text} {text}

View File

@@ -20,7 +20,6 @@ const UpgradeFootnote = ({ deadline, href }) => {
const upgradeLink = ( const upgradeLink = (
<Hyperlink <Hyperlink
id="upgrade-link"
style={{ textDecoration: 'underline' }} style={{ textDecoration: 'underline' }}
destination={href} destination={href}
className="text-reset" className="text-reset"

View File

@@ -76,11 +76,6 @@ const messages = defineMessages({
defaultMessage: 'Dashboard', defaultMessage: 'Dashboard',
description: 'Link to users dashboard', description: 'Link to users dashboard',
}, },
dashboardInfo: {
id: 'courseCelebration.dashboardInfo', // for historical reasons
defaultMessage: 'You can access this course and its materials on your {dashboardLink}.',
description: "Text that precedes link to learner's dashboard",
},
endOfCourseDescription: { endOfCourseDescription: {
id: 'courseExit.endOfCourseDescription', id: 'courseExit.endOfCourseDescription',
defaultMessage: 'Unfortunately, you are not currently eligible for a certificate. You need to receive a passing grade to be eligible for a certificate.', defaultMessage: 'Unfortunately, you are not currently eligible for a certificate. You need to receive a passing grade to be eligible for a certificate.',

View File

@@ -17,7 +17,6 @@ const CELEBRATION_STATUSES = [
'audit_passing', 'audit_passing',
'downloadable', 'downloadable',
'earned_but_not_available', 'earned_but_not_available',
'not_earned_but_available_date',
'honor_passing', 'honor_passing',
'requesting', 'requesting',
'unverified', 'unverified',

View File

@@ -54,8 +54,6 @@ const SidebarProvider: React.FC<Props> = ({
}, [courseId]); }, [courseId]);
useEffect(() => { useEffect(() => {
window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true');
window.sessionStorage.setItem(`notificationTrayStatus.${courseId}`, 'open');
setHideDiscussionbar(!isDiscussionbarAvailable); setHideDiscussionbar(!isDiscussionbarAvailable);
setHideNotificationbar(!isNotificationbarAvailable); setHideNotificationbar(!isNotificationbarAvailable);
if (initialSidebar && currentSidebar !== initialSidebar) { if (initialSidebar && currentSidebar !== initialSidebar) {

View File

@@ -1,13 +1,11 @@
import React from 'react'; import React from 'react';
import { fireEvent } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getSessionStorage, setSessionStorage } from '../../../../../../data/sessionStorage';
import { import {
initializeMockApp, initializeTestStore, render, screen, initializeMockApp, initializeTestStore, render, screen,
} from '../../../../../../setupTest'; } from '../../../../../../setupTest';
@@ -16,19 +14,11 @@ import { buildTopicsFromUnits } from '../../../../../data/__factories__/discussi
import { getCourseDiscussionTopics } from '../../../../../data/thunks'; import { getCourseDiscussionTopics } from '../../../../../data/thunks';
import SidebarContext from '../../../SidebarContext'; import SidebarContext from '../../../SidebarContext';
import DiscussionsNotificationsSidebar from '../DiscussionsNotificationsSidebar'; import DiscussionsNotificationsSidebar from '../DiscussionsNotificationsSidebar';
import DiscussionsNotificationsTrigger from '../DiscussionsNotificationsTrigger';
import DiscussionsWidget from './DiscussionsWidget'; import DiscussionsWidget from './DiscussionsWidget';
initializeMockApp(); initializeMockApp();
jest.mock('@edx/frontend-platform/analytics'); jest.mock('@edx/frontend-platform/analytics');
jest.mock('../../../../../../data/sessionStorage', () => ({
getSessionStorage: jest.fn(),
setSessionStorage: jest.fn(),
}));
const onClickMock = jest.fn();
describe('DiscussionsWidget', () => { describe('DiscussionsWidget', () => {
let axiosMock; let axiosMock;
let mockData; let mockData;
@@ -91,34 +81,4 @@ describe('DiscussionsWidget', () => {
expect(screen.queryByText('Back to course')).toBeInTheDocument(); expect(screen.queryByText('Back to course')).toBeInTheDocument();
expect(sendTrackEvent).toHaveBeenCalledTimes(1); expect(sendTrackEvent).toHaveBeenCalledTimes(1);
}); });
it('should open notification tray if closed', () => {
(getSessionStorage as jest.Mock).mockReturnValue('closed');
renderWithProvider(() => <DiscussionsNotificationsTrigger onClick={onClickMock} />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(setSessionStorage).toHaveBeenCalledWith(
`notificationTrayStatus.${courseId}`,
'open',
);
expect(onClickMock).toHaveBeenCalled();
});
it('should close notification tray if open', () => {
(getSessionStorage as jest.Mock).mockReturnValue('open');
renderWithProvider(() => <DiscussionsNotificationsTrigger onClick={onClickMock} />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(setSessionStorage).toHaveBeenCalledWith(
`notificationTrayStatus.${courseId}`,
'open',
);
expect(onClickMock).toHaveBeenCalled();
});
}); });

View File

@@ -13,17 +13,17 @@ import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import PageLoading from '@src/generic/PageLoading'; import PageLoading from '@src/generic/PageLoading';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '@src/alerts/sequence-alerts/hooks'; import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '@src/alerts/sequence-alerts/hooks';
import SequenceContainerSlot from '@src/plugin-slots/SequenceContainerSlot'; import SequenceContainerSlot from '../../../plugin-slots/SequenceContainerSlot';
import { CourseOutlineSidebarSlot } from '@src/plugin-slots/CourseOutlineSidebarSlot';
import { CourseOutlineSidebarTriggerSlot } from '@src/plugin-slots/CourseOutlineSidebarTriggerSlot';
import { NotificationsDiscussionsSidebarSlot } from '@src/plugin-slots/NotificationsDiscussionsSidebarSlot';
import SequenceNavigationSlot from '@src/plugin-slots/SequenceNavigationSlot';
import { getCoursewareOutlineSidebarSettings } from '../../data/selectors';
import CourseLicense from '../course-license'; import CourseLicense from '../course-license';
import { NotificationsDiscussionsSidebarSlot } from '../../../plugin-slots/NotificationsDiscussionsSidebarSlot';
import messages from './messages'; import messages from './messages';
import HiddenAfterDue from './hidden-after-due'; import HiddenAfterDue from './hidden-after-due';
import { UnitNavigation } from './sequence-navigation'; import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
import SequenceContent from './SequenceContent'; import SequenceContent from './SequenceContent';
import { CourseOutlineSidebarSlot } from '../../../plugin-slots/CourseOutlineSidebarSlot';
import { CourseOutlineSidebarTriggerSlot } from '../../../plugin-slots/CourseOutlineSidebarTriggerSlot';
const Sequence = ({ const Sequence = ({
unitId, unitId,
@@ -47,7 +47,7 @@ const Sequence = ({
const unit = useModel('units', unitId); const unit = useModel('units', unitId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus); const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const sequenceMightBeUnit = useSelector(state => state.courseware.sequenceMightBeUnit); const sequenceMightBeUnit = useSelector(state => state.courseware.sequenceMightBeUnit);
const { enableNavigationSidebar: isEnabledOutlineSidebar } = useSelector(getCoursewareOutlineSidebarSettings);
const handleNext = () => { const handleNext = () => {
const nextIndex = sequence.unitIds.indexOf(unitId) + 1; const nextIndex = sequence.unitIds.indexOf(unitId) + 1;
const newUnitId = sequence.unitIds[nextIndex]; const newUnitId = sequence.unitIds[nextIndex];
@@ -90,30 +90,6 @@ const Sequence = ({
sendTrackingLogEvent(eventName, payload); sendTrackingLogEvent(eventName, payload);
}; };
/* istanbul ignore next */
const nextHandler = () => {
logEvent('edx.ui.lms.sequence.next_selected', 'top');
handleNext();
};
/* istanbul ignore next */
const previousHandler = () => {
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
handlePrevious();
};
/* istanbul ignore next */
const onNavigate = (destinationUnitId) => {
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
handleNavigate(destinationUnitId);
};
const sequenceNavProps = {
nextHandler,
previousHandler,
onNavigate,
};
useSequenceBannerTextAlert(sequenceId); useSequenceBannerTextAlert(sequenceId);
useSequenceEntranceExamAlert(courseId, sequenceId, intl); useSequenceEntranceExamAlert(courseId, sequenceId, intl);
@@ -194,25 +170,30 @@ const Sequence = ({
/> />
<CourseOutlineSidebarSlot /> <CourseOutlineSidebarSlot />
<div className="sequence w-100"> <div className="sequence w-100">
<div className="sequence-navigation-container"> {!isEnabledOutlineSidebar && (
{/** <div className="sequence-navigation-container">
SequenceNavigationSlot renders nothing by default. <SequenceNavigation
However, we still pass nextHandler, previousHandler, and onNavigate, sequenceId={sequenceId}
because, as per the slot's contract, if this slot is replaced unitId={unitId}
with the default SequenceNavigation component, these props are required. nextHandler={() => {
These handlers are excluded from test coverage via istanbul ignore, logEvent('edx.ui.lms.sequence.next_selected', 'top');
since they are not used unless the slot is overridden. handleNext();
*/} }}
<SequenceNavigationSlot onNavigate={(destinationUnitId) => {
sequenceId={sequenceId} logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
unitId={unitId} handleNavigate(destinationUnitId);
{...{ }}
...sequenceNavProps, previousHandler={() => {
nextSequenceHandler, logEvent('edx.ui.lms.sequence.previous_selected', 'top');
handleNavigate, handlePrevious();
}} }}
/> {...{
</div> nextSequenceHandler,
handleNavigate,
}}
/>
</div>
)}
<div className="unit-container flex-grow-1 pt-4"> <div className="unit-container flex-grow-1 pt-4">
<SequenceContent <SequenceContent
@@ -222,6 +203,7 @@ const Sequence = ({
unitId={unitId} unitId={unitId}
unitLoadedHandler={handleUnitLoaded} unitLoadedHandler={handleUnitLoaded}
isOriginalUserStaff={originalUserIsStaff} isOriginalUserStaff={originalUserIsStaff}
isEnabledOutlineSidebar={isEnabledOutlineSidebar}
renderUnitNavigation={renderUnitNavigation} renderUnitNavigation={renderUnitNavigation}
/> />
{unitHasLoaded && renderUnitNavigation(false)} {unitHasLoaded && renderUnitNavigation(false)}
@@ -235,20 +217,18 @@ const Sequence = ({
if (sequenceStatus === 'loaded') { if (sequenceStatus === 'loaded') {
return ( return (
<> <div>
<div className="d-flex flex-column flex-grow-1 justify-content-center"> <SequenceExamWrapper
<SequenceExamWrapper sequence={sequence}
sequence={sequence} courseId={courseId}
courseId={courseId} isStaff={isStaff}
isStaff={isStaff} originalUserIsStaff={originalUserIsStaff}
originalUserIsStaff={originalUserIsStaff} canAccessProctoredExams={canAccessProctoredExams}
canAccessProctoredExams={canAccessProctoredExams} >
> {defaultContent}
{defaultContent} </SequenceExamWrapper>
</SequenceExamWrapper>
</div>
<CourseLicense license={license || undefined} /> <CourseLicense license={license || undefined} />
</> </div>
); );
} }

View File

@@ -24,6 +24,7 @@ describe('Sequence', () => {
{ type: 'vertical' }, { type: 'vertical' },
{ courseId: courseMetadata.id }, { courseId: courseMetadata.id },
)); ));
const enableNavigationSidebar = { enable_navigation_sidebar: false };
beforeAll(async () => { beforeAll(async () => {
const store = await initializeTestStore({ courseMetadata, unitBlocks }); const store = await initializeTestStore({ courseMetadata, unitBlocks });
@@ -95,6 +96,7 @@ describe('Sequence', () => {
unitBlocks, unitBlocks,
sequenceBlocks, sequenceBlocks,
sequenceMetadata, sequenceMetadata,
enableNavigationSidebar: { enable_navigation_sidebar: true },
}, false); }, false);
const { container } = render( const { container } = render(
<SidebarWrapper overrideData={{ sequenceId: sequenceBlocks[0].id }} />, <SidebarWrapper overrideData={{ sequenceId: sequenceBlocks[0].id }} />,
@@ -129,7 +131,7 @@ describe('Sequence', () => {
{ courseId: courseMetadata.id, unitBlocks, sequenceBlock: sequenceBlocks[0] }, { courseId: courseMetadata.id, unitBlocks, sequenceBlock: sequenceBlocks[0] },
)]; )];
const testStore = await initializeTestStore({ const testStore = await initializeTestStore({
courseMetadata, unitBlocks, sequenceBlocks, sequenceMetadata, courseMetadata, unitBlocks, sequenceBlocks, sequenceMetadata, enableNavigationSidebar,
}, false); }, false);
render( render(
<Sequence {...mockData} {...{ sequenceId: sequenceBlocks[0].id }} />, <Sequence {...mockData} {...{ sequenceId: sequenceBlocks[0].id }} />,
@@ -188,7 +190,7 @@ describe('Sequence', () => {
beforeAll(async () => { beforeAll(async () => {
testStore = await initializeTestStore({ testStore = await initializeTestStore({
courseMetadata, unitBlocks, sequenceBlocks, courseMetadata, unitBlocks, sequenceBlocks, enableNavigationSidebar,
}, false); }, false);
}); });
@@ -364,6 +366,7 @@ describe('Sequence', () => {
unitBlocks, unitBlocks,
sequenceBlocks: testSequenceBlocks, sequenceBlocks: testSequenceBlocks,
sequenceMetadata: testSequenceMetadata, sequenceMetadata: testSequenceMetadata,
enableNavigationSidebar,
}, false); }, false);
const testData = { const testData = {
...mockData, ...mockData,

View File

@@ -16,6 +16,7 @@ const SequenceContent = ({
unitId, unitId,
unitLoadedHandler, unitLoadedHandler,
isOriginalUserStaff, isOriginalUserStaff,
isEnabledOutlineSidebar,
renderUnitNavigation, renderUnitNavigation,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@@ -62,6 +63,7 @@ const SequenceContent = ({
id={unitId} id={unitId}
onLoaded={unitLoadedHandler} onLoaded={unitLoadedHandler}
isOriginalUserStaff={isOriginalUserStaff} isOriginalUserStaff={isOriginalUserStaff}
isEnabledOutlineSidebar={isEnabledOutlineSidebar}
renderUnitNavigation={renderUnitNavigation} renderUnitNavigation={renderUnitNavigation}
/> />
); );
@@ -74,6 +76,7 @@ SequenceContent.propTypes = {
unitId: PropTypes.string, unitId: PropTypes.string,
unitLoadedHandler: PropTypes.func.isRequired, unitLoadedHandler: PropTypes.func.isRequired,
isOriginalUserStaff: PropTypes.bool.isRequired, isOriginalUserStaff: PropTypes.bool.isRequired,
isEnabledOutlineSidebar: PropTypes.bool.isRequired,
renderUnitNavigation: PropTypes.func.isRequired, renderUnitNavigation: PropTypes.func.isRequired,
}; };

View File

@@ -15,7 +15,6 @@ describe('Sequence Content', () => {
sequenceId: courseware.sequenceId, sequenceId: courseware.sequenceId,
unitId: models.sequences[courseware.sequenceId].unitIds[0], unitId: models.sequences[courseware.sequenceId].unitIds[0],
unitLoadedHandler: () => { }, unitLoadedHandler: () => { },
renderUnitNavigation: () => { },
}; };
}); });
@@ -39,7 +38,7 @@ describe('Sequence Content', () => {
}); });
it('displays message for no content', () => { it('displays message for no content', () => {
render(<SequenceContent {...mockData} unitId="" />, { wrapWithRouter: true }); render(<SequenceContent {...mockData} unitId={null} />, { wrapWithRouter: true });
expect(screen.getByText('There is no content here.')).toBeInTheDocument(); expect(screen.getByText('There is no content here.')).toBeInTheDocument();
}); });
}); });

View File

@@ -1,7 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react';
import { ErrorPage } from '@edx/frontend-platform/react'; import { ErrorPage } from '@edx/frontend-platform/react';
import { ModalDialog } from '@openedx/paragon'; import { StrictDict } from '@edx/react-unit-test-utils';
import { ModalDialog, Modal } from '@openedx/paragon';
import { ContentIFrameLoaderSlot } from '../../../../plugin-slots/ContentIFrameLoaderSlot'; import { ContentIFrameLoaderSlot } from '../../../../plugin-slots/ContentIFrameLoaderSlot';
import * as hooks from './hooks'; import * as hooks from './hooks';
@@ -20,10 +22,10 @@ export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *; autoplay *' 'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *; autoplay *'
); );
export const testIDs = { export const testIDs = StrictDict({
contentIFrame: 'content-iframe-test-id', contentIFrame: 'content-iframe-test-id',
modalIFrame: 'modal-iframe-test-id', modalIFrame: 'modal-iframe-test-id',
}; });
const ContentIFrame = ({ const ContentIFrame = ({
iframeUrl, iframeUrl,
@@ -63,6 +65,21 @@ const ContentIFrame = ({
onLoad: handleIFrameLoad, onLoad: handleIFrameLoad,
}; };
let modalContent;
if (modalOptions.isOpen) {
modalContent = modalOptions.body
? <div className="unit-modal">{ modalOptions.body }</div>
: (
<iframe
title={modalOptions.title}
allow={IFRAME_FEATURE_POLICY}
frameBorder="0"
src={modalOptions.url}
style={{ width: '100%', height: modalOptions.height }}
/>
);
}
return ( return (
<> <>
{(shouldShowContent && !hasLoaded) && ( {(shouldShowContent && !hasLoaded) && (
@@ -73,30 +90,29 @@ const ContentIFrame = ({
<iframe title={title} {...contentIFrameProps} data-testid={testIDs.contentIFrame} /> <iframe title={title} {...contentIFrameProps} data-testid={testIDs.contentIFrame} />
</div> </div>
)} )}
{modalOptions.isOpen {modalOptions.isOpen && (modalOptions.isFullscreen
&& ( ? (
<ModalDialog <ModalDialog
dialogClassName="modal-lti" dialogClassName="modal-lti"
onClose={handleModalClose} onClose={handleModalClose}
size={modalOptions.isFullscreen ? 'fullscreen' : 'md'} size="fullscreen"
isOpen isOpen
hasCloseButton={false} hasCloseButton={false}
> >
<ModalDialog.Body className={modalOptions.modalBodyClassName}> <ModalDialog.Body className={modalOptions.modalBodyClassName}>
{modalOptions.body {modalContent}
? <div className="unit-modal">{ modalOptions.body }</div>
: (
<iframe
title={modalOptions.title}
allow={IFRAME_FEATURE_POLICY}
frameBorder="0"
src={modalOptions.url}
style={{ width: '100%', height: modalOptions.height }}
/>
)}
</ModalDialog.Body> </ModalDialog.Body>
</ModalDialog> </ModalDialog>
)}
) : (
<Modal
body={modalContent}
dialogClassName="modal-lti"
onClose={handleModalClose}
open
/>
)
)}
</> </>
); );
}; };

View File

@@ -1,11 +1,26 @@
import { render, screen } from '@testing-library/react'; import React from 'react';
import { ErrorPage } from '@edx/frontend-platform/react';
import { ModalDialog, Modal } from '@openedx/paragon';
import { shallow } from '@edx/react-unit-test-utils';
import PageLoading from '@src/generic/PageLoading';
import { ContentIFrameLoaderSlot } from '@src/plugin-slots/ContentIFrameLoaderSlot';
import * as hooks from './hooks'; import * as hooks from './hooks';
import ContentIFrame, { IFRAME_FEATURE_POLICY } from './ContentIFrame'; import ContentIFrame, { IFRAME_FEATURE_POLICY, testIDs } from './ContentIFrame';
jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: () => <div>ErrorPage</div> })); jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: 'ErrorPage' }));
jest.mock('@src/generic/PageLoading', () => jest.fn(() => <div>PageLoading</div>)); jest.mock('@openedx/paragon', () => jest.requireActual('@edx/react-unit-test-utils')
.mockComponents({
Modal: 'Modal',
ModalDialog: {
Body: 'ModalDialog.Body',
},
}));
jest.mock('@src/generic/PageLoading', () => 'PageLoading');
jest.mock('./hooks', () => ({ jest.mock('./hooks', () => ({
useIFrameBehavior: jest.fn(), useIFrameBehavior: jest.fn(),
@@ -53,13 +68,14 @@ const props = {
title: 'test-title', title: 'test-title',
}; };
let el;
describe('ContentIFrame Component', () => { describe('ContentIFrame Component', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
describe('behavior', () => { describe('behavior', () => {
beforeEach(() => { beforeEach(() => {
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
}); });
it('initializes iframe behavior hook', () => { it('initializes iframe behavior hook', () => {
expect(hooks.useIFrameBehavior).toHaveBeenCalledWith({ expect(hooks.useIFrameBehavior).toHaveBeenCalledWith({
@@ -74,61 +90,61 @@ describe('ContentIFrame Component', () => {
}); });
}); });
describe('output', () => { describe('output', () => {
let component;
describe('if shouldShowContent', () => { describe('if shouldShowContent', () => {
describe('if not hasLoaded', () => { describe('if not hasLoaded', () => {
it('displays errorPage if showError', () => { it('displays errorPage if showError', () => {
hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, showError: true }); hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, showError: true });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
const errorPage = screen.getByText('ErrorPage'); expect(el.instance.findByType(ErrorPage).length).toEqual(1);
expect(errorPage).toBeInTheDocument();
}); });
it('displays PageLoading component if not showError', () => { it('displays PageLoading component if not showError', () => {
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
const pageLoading = screen.getByText('PageLoading'); [component] = el.instance.findByType(ContentIFrameLoaderSlot);
expect(pageLoading).toBeInTheDocument(); expect(component.props.loadingMessage).toEqual(props.loadingMessage);
}); });
}); });
describe('hasLoaded', () => { describe('hasLoaded', () => {
it('does not display PageLoading or ErrorPage', () => { it('does not display PageLoading or ErrorPage', () => {
hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, hasLoaded: true }); hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, hasLoaded: true });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
const pageLoading = screen.queryByText('PageLoading'); expect(el.instance.findByType(PageLoading).length).toEqual(0);
expect(pageLoading).toBeNull(); expect(el.instance.findByType(ErrorPage).length).toEqual(0);
const errorPage = screen.queryByText('ErrorPage');
expect(errorPage).toBeNull();
}); });
}); });
it('display iframe with props from hooks', () => { it('display iframe with props from hooks', () => {
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
const iframe = screen.getByTitle(props.title); [component] = el.instance.findByTestId(testIDs.contentIFrame);
expect(iframe).toBeInTheDocument(); expect(component.props).toEqual({
expect(iframe).toHaveAttribute('id', props.elementId); allow: IFRAME_FEATURE_POLICY,
expect(iframe).toHaveAttribute('src', props.iframeUrl); allowFullScreen: true,
expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY); scrolling: 'no',
expect(iframe).toHaveAttribute('allowfullscreen', ''); referrerPolicy: 'origin',
expect(iframe).toHaveAttribute('scrolling', 'no'); title: props.title,
expect(iframe).toHaveAttribute('referrerpolicy', 'origin'); id: props.elementId,
src: props.iframeUrl,
height: iframeBehavior.iframeHeight,
onLoad: iframeBehavior.handleIFrameLoad,
'data-testid': testIDs.contentIFrame,
});
}); });
}); });
describe('if not shouldShowContent', () => { describe('if not shouldShowContent', () => {
it('does not show PageLoading, ErrorPage, or unit-iframe-wrapper', () => { it('does not show PageLoading, ErrorPage, or unit-iframe-wrapper', () => {
render(<ContentIFrame {...{ ...props, shouldShowContent: false }} />); el = shallow(<ContentIFrame {...{ ...props, shouldShowContent: false }} />);
expect(screen.queryByText('PageLoading')).toBeNull(); expect(el.instance.findByType(PageLoading).length).toEqual(0);
expect(screen.queryByText('ErrorPage')).toBeNull(); expect(el.instance.findByType(ErrorPage).length).toEqual(0);
expect(screen.queryByTitle(props.title)).toBeNull(); expect(el.instance.findByTestId(testIDs.contentIFrame).length).toEqual(0);
}); });
}); });
it('does not display modal if modalOptions returns isOpen: false', () => { it('does not display modal if modalOptions returns isOpen: false', () => {
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
const modal = screen.queryByRole('dialog'); expect(el.instance.findByType(Modal).length).toEqual(0);
expect(modal).toBeNull();
}); });
describe('if modalOptions.isOpen', () => { describe('if modalOptions.isOpen', () => {
const testModalOpenAndHandleClose = () => { const testModalOpenAndHandleClose = () => {
it('closes modal on close button click', () => { test('Modal component isOpen, with handleModalClose from hook', () => {
const closeButton = screen.getByTestId('modal-backdrop'); expect(component.props.onClose).toEqual(modalIFrameData.handleModalClose);
closeButton.click();
expect(modalIFrameData.handleModalClose).toHaveBeenCalled();
}); });
}; };
describe('fullscreen modal', () => { describe('fullscreen modal', () => {
@@ -138,13 +154,14 @@ describe('ContentIFrame Component', () => {
...modalIFrameData, ...modalIFrameData,
modalOptions: { ...modalOptions.withBody, isFullscreen: true }, modalOptions: { ...modalOptions.withBody, isFullscreen: true },
}); });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
[component] = el.instance.findByType(ModalDialog);
}); });
it('displays Modal with div wrapping provided body content if modal.body is provided', () => { it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
const dialog = screen.getByRole('dialog'); const content = component.findByType(ModalDialog.Body)[0].children[0];
expect(dialog).toBeInTheDocument(); expect(content.matches(shallow(
const modalBody = screen.getByText(modalOptions.withBody.body); <div className="unit-modal">{modalOptions.withBody.body}</div>,
expect(modalBody).toBeInTheDocument(); ))).toEqual(true);
}); });
testModalOpenAndHandleClose(); testModalOpenAndHandleClose();
}); });
@@ -155,42 +172,53 @@ describe('ContentIFrame Component', () => {
...modalIFrameData, ...modalIFrameData,
modalOptions: { ...modalOptions.withUrl, isFullscreen: true }, modalOptions: { ...modalOptions.withUrl, isFullscreen: true },
}); });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
}); [component] = el.instance.findByType(ModalDialog);
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
const iframe = screen.getByTitle(modalOptions.withUrl.title);
expect(iframe).toBeInTheDocument();
expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
expect(iframe).toHaveAttribute('src', modalOptions.withUrl.url);
}); });
testModalOpenAndHandleClose(); testModalOpenAndHandleClose();
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
const content = component.findByType(ModalDialog.Body)[0].children[0];
expect(content.matches(shallow(
<iframe
title={modalOptions.withUrl.title}
allow={IFRAME_FEATURE_POLICY}
frameBorder="0"
src={modalOptions.withUrl.url}
style={{ width: '100%', height: modalOptions.withUrl.height }}
/>,
))).toEqual(true);
});
}); });
}); });
describe('body modal', () => { describe('body modal', () => {
beforeEach(() => { beforeEach(() => {
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withBody }); hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withBody });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
[component] = el.instance.findByType(Modal);
}); });
it('displays Modal with div wrapping provided body content if modal.body is provided', () => { it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
const dialog = screen.getByRole('dialog'); expect(component.props.body).toEqual(<div className="unit-modal">{modalOptions.withBody.body}</div>);
expect(dialog).toBeInTheDocument();
const modalBody = screen.getByText(modalOptions.withBody.body);
expect(modalBody).toBeInTheDocument();
}); });
testModalOpenAndHandleClose(); testModalOpenAndHandleClose();
}); });
describe('url modal', () => { describe('url modal', () => {
beforeEach(() => { beforeEach(() => {
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withUrl }); hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withUrl });
render(<ContentIFrame {...props} />); el = shallow(<ContentIFrame {...props} />);
}); [component] = el.instance.findByType(Modal);
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
const iframe = screen.getByTitle(modalOptions.withUrl.title);
expect(iframe).toBeInTheDocument();
expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
expect(iframe).toHaveAttribute('src', modalOptions.withUrl.url);
}); });
testModalOpenAndHandleClose(); testModalOpenAndHandleClose();
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
expect(component.props.body).toEqual(
<iframe
title={modalOptions.withUrl.title}
allow={IFRAME_FEATURE_POLICY}
frameBorder="0"
src={modalOptions.withUrl.url}
style={{ width: '100%', height: modalOptions.withUrl.height }}
/>,
);
});
}); });
}); });
}); });

View File

@@ -1,15 +1,22 @@
import { render, screen } from '@testing-library/react'; import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import PageLoading from '@src/generic/PageLoading';
import { GatedUnitContentMessageSlot } from '@src/plugin-slots/GatedUnitContentMessageSlot';
import messages from '../messages';
import HonorCode from '../honor-code';
import LockPaywall from '../lock-paywall';
import hooks from './hooks'; import hooks from './hooks';
import { modelKeys } from './constants'; import { modelKeys } from './constants';
import UnitSuspense from './UnitSuspense'; import UnitSuspense from './UnitSuspense';
jest.mock('@edx/frontend-platform/i18n', () => ({ jest.mock('@edx/frontend-platform/i18n', () => ({
...jest.requireActual('@edx/frontend-platform/i18n'),
defineMessages: m => m, defineMessages: m => m,
useIntl: () => ({ formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage }),
})); }));
jest.mock('react', () => ({ jest.mock('react', () => ({
@@ -17,9 +24,10 @@ jest.mock('react', () => ({
Suspense: 'Suspense', Suspense: 'Suspense',
})); }));
jest.mock('../honor-code', () => jest.fn(() => <div>HonorCode</div>)); jest.mock('../honor-code', () => 'HonorCode');
jest.mock('../lock-paywall', () => jest.fn(() => <div>LockPaywall</div>)); jest.mock('../lock-paywall', () => 'LockPaywall');
jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() })); jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() }));
jest.mock('@src/generic/PageLoading', () => 'PageLoading');
jest.mock('./hooks', () => ({ jest.mock('./hooks', () => ({
useShouldDisplayHonorCode: jest.fn(() => false), useShouldDisplayHonorCode: jest.fn(() => false),
@@ -38,6 +46,7 @@ const props = {
id: 'test-id', id: 'test-id',
}; };
let el;
describe('UnitSuspense component', () => { describe('UnitSuspense component', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -45,7 +54,7 @@ describe('UnitSuspense component', () => {
}); });
describe('behavior', () => { describe('behavior', () => {
it('initializes models', () => { it('initializes models', () => {
render(<IntlProvider locale="en"><UnitSuspense {...props} /></IntlProvider>); el = shallow(<UnitSuspense {...props} />);
const { calls } = useModel.mock; const { calls } = useModel.mock;
const [unitCall] = calls.filter(call => call[0] === modelKeys.units); const [unitCall] = calls.filter(call => call[0] === modelKeys.units);
const [metaCall] = calls.filter(call => call[0] === modelKeys.coursewareMeta); const [metaCall] = calls.filter(call => call[0] === modelKeys.coursewareMeta);
@@ -57,9 +66,8 @@ describe('UnitSuspense component', () => {
describe('LockPaywall', () => { describe('LockPaywall', () => {
const testNoPaywall = () => { const testNoPaywall = () => {
it('does not display LockPaywall', () => { it('does not display LockPaywall', () => {
render(<IntlProvider locale="en"><UnitSuspense {...props} /></IntlProvider>); el = shallow(<UnitSuspense {...props} />);
const lockPaywall = screen.queryByText('LockPaywall'); expect(el.instance.findByType(LockPaywall).length).toEqual(0);
expect(lockPaywall).toBeNull();
}); });
}; };
describe('gating not enabled', () => { testNoPaywall(); }); describe('gating not enabled', () => { testNoPaywall(); });
@@ -70,29 +78,29 @@ describe('UnitSuspense component', () => {
describe('gating enabled, gated content included', () => { describe('gating enabled, gated content included', () => {
beforeEach(() => { mockModels(true, true); }); beforeEach(() => { mockModels(true, true); });
it('displays LockPaywall in Suspense wrapper with PageLoading fallback', () => { it('displays LockPaywall in Suspense wrapper with PageLoading fallback', () => {
hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false); el = shallow(<UnitSuspense {...props} />);
render(<IntlProvider locale="en"><UnitSuspense {...props} /></IntlProvider>); const [component] = el.instance.findByType(GatedUnitContentMessageSlot);
const lockPaywall = screen.getByText('LockPaywall'); expect(component.parent.type).toEqual('Suspense');
expect(lockPaywall).toBeInTheDocument(); expect(component.parent.props.fallback)
const suspenseWrapper = lockPaywall.closest('suspense'); .toEqual(<PageLoading srMessage={formatMessage(messages.loadingLockedContent)} />);
expect(suspenseWrapper).toBeInTheDocument(); expect(component.props.courseId).toEqual(props.courseId);
}); });
}); });
}); });
describe('HonorCode', () => { describe('HonorCode', () => {
it('does not display HonorCode if useShouldDisplayHonorCode => false', () => { it('does not display HonorCode if useShouldDisplayHonorCode => false', () => {
hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false); hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false);
render(<IntlProvider locale="en"><UnitSuspense {...props} /></IntlProvider>); el = shallow(<UnitSuspense {...props} />);
const honorCode = screen.queryByText('HonorCode'); expect(el.instance.findByType(HonorCode).length).toEqual(0);
expect(honorCode).toBeNull();
}); });
it('displays HonorCode component in Suspense wrapper with PageLoading fallback if shouldDisplayHonorCode', () => { it('displays HonorCode component in Suspense wrapper with PageLoading fallback if shouldDisplayHonorCode', () => {
hooks.useShouldDisplayHonorCode.mockReturnValueOnce(true); hooks.useShouldDisplayHonorCode.mockReturnValueOnce(true);
render(<IntlProvider locale="en"><UnitSuspense {...props} /></IntlProvider>); el = shallow(<UnitSuspense {...props} />);
const honorCode = screen.getByText('HonorCode'); const [component] = el.instance.findByType(HonorCode);
expect(honorCode).toBeInTheDocument(); expect(component.parent.type).toEqual('Suspense');
const suspenseWrapper = honorCode.closest('suspense'); expect(component.parent.props.fallback)
expect(suspenseWrapper).toBeInTheDocument(); .toEqual(<PageLoading srMessage={formatMessage(messages.loadingHonorCode)} />);
expect(component.props.courseId).toEqual(props.courseId);
}); });
}); });
}); });

View File

@@ -1,25 +1,26 @@
export const modelKeys = { import { StrictDict } from '@edx/react-unit-test-utils/dist';
export const modelKeys = StrictDict({
units: 'units', units: 'units',
coursewareMeta: 'coursewareMeta', coursewareMeta: 'coursewareMeta',
} as const; });
export const views = { export const views = StrictDict({
student: 'student_view', student: 'student_view',
public: 'public_view', public: 'public_view',
} as const; });
export const loadingState = 'loading'; export const loadingState = 'loading';
export const messageTypes = { export const messageTypes = StrictDict({
modal: 'plugin.modal', modal: 'plugin.modal',
resize: 'plugin.resize', resize: 'plugin.resize',
videoFullScreen: 'plugin.videoFullScreen', videoFullScreen: 'plugin.videoFullScreen',
autoAdvance: 'plugin.autoAdvance', });
} as const;
export default { export default StrictDict({
modelKeys, modelKeys,
views, views,
loadingState, loadingState,
messageTypes, messageTypes,
}; });

View File

@@ -1,13 +1,19 @@
import React from 'react'; import React from 'react';
import { logError } from '@edx/frontend-platform/logging'; import { logError } from '@edx/frontend-platform/logging';
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
import { useExamAccessToken, useFetchExamAccessToken, useIsExam } from '@edx/frontend-lib-special-exams'; import { useExamAccessToken, useFetchExamAccessToken, useIsExam } from '@edx/frontend-lib-special-exams';
export const stateKeys = StrictDict({
accessToken: 'accessToken',
blockAccess: 'blockAccess',
});
const useExamAccess = ({ const useExamAccess = ({
id, id,
}) => { }) => {
const isExam = useIsExam(); const isExam = useIsExam();
const [blockAccess, setBlockAccess] = React.useState(isExam); const [blockAccess, setBlockAccess] = useKeyedState(stateKeys.blockAccess, isExam);
const fetchExamAccessToken = useFetchExamAccessToken(); const fetchExamAccessToken = useFetchExamAccessToken();

View File

@@ -1,28 +1,25 @@
import React, { useState } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { useDispatch, useSelector } from 'react-redux'; import React from 'react';
import { useNavigate } from 'react-router-dom'; import { useDispatch } from 'react-redux';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
import { logError } from '@edx/frontend-platform/logging'; import { logError } from '@edx/frontend-platform/logging';
import { fetchCourse } from '@src/courseware/data'; import { fetchCourse } from '@src/courseware/data';
import { processEvent } from '@src/course-home/data/thunks'; import { processEvent } from '@src/course-home/data/thunks';
import { useEventListener } from '@src/generic/hooks'; import { useEventListener } from '@src/generic/hooks';
import { getSequenceId } from '@src/courseware/data/selectors';
import { useModel } from '@src/generic/model-store';
import { useSequenceNavigationMetadata } from '@src/courseware/course/sequence/sequence-navigation/hooks';
import { messageTypes } from '../constants'; import { messageTypes } from '../constants';
import useLoadBearingHook from './useLoadBearingHook'; import useLoadBearingHook from './useLoadBearingHook';
export const iframeBehaviorState = { export const stateKeys = StrictDict({
iframeHeight: (val) => useState<number>(val), // eslint-disable-line iframeHeight: 'iframeHeight',
hasLoaded: (val) => useState<boolean>(val), // eslint-disable-line hasLoaded: 'hasLoaded',
showError: (val) => useState<boolean>(val), // eslint-disable-line showError: 'showError',
windowTopOffset: (val) => useState<number | null>(val), // eslint-disable-line windowTopOffset: 'windowTopOffset',
} as const; });
const useIFrameBehavior = ({ const useIFrameBehavior = ({
elementId, elementId,
@@ -34,29 +31,23 @@ const useIFrameBehavior = ({
useLoadBearingHook(id); useLoadBearingHook(id);
const dispatch = useDispatch(); const dispatch = useDispatch();
const activeSequenceId = useSelector(getSequenceId);
const navigate = useNavigate();
const activeSequence = useModel('sequences', activeSequenceId);
const activeUnitId = activeSequence.unitIds.length > 0
? activeSequence.unitIds[activeSequence.activeUnitIndex] : null;
const { isLastUnit, nextLink } = useSequenceNavigationMetadata(activeSequenceId, activeUnitId);
const [iframeHeight, setIframeHeight] = iframeBehaviorState.iframeHeight(0); const [iframeHeight, setIframeHeight] = useKeyedState(stateKeys.iframeHeight, 0);
const [hasLoaded, setHasLoaded] = iframeBehaviorState.hasLoaded(false); const [hasLoaded, setHasLoaded] = useKeyedState(stateKeys.hasLoaded, false);
const [showError, setShowError] = iframeBehaviorState.showError(false); const [showError, setShowError] = useKeyedState(stateKeys.showError, false);
const [windowTopOffset, setWindowTopOffset] = iframeBehaviorState.windowTopOffset(null); const [windowTopOffset, setWindowTopOffset] = useKeyedState(stateKeys.windowTopOffset, null);
React.useEffect(() => { React.useEffect(() => {
const frame = document.getElementById(elementId) as HTMLIFrameElement | null; const frame = document.getElementById(elementId);
const { hash } = window.location; const { hash } = window.location;
if (hash) { if (hash) {
// The url hash will be sent to LMS-served iframe in order to find the location of the // The url hash will be sent to LMS-served iframe in order to find the location of the
// hash within the iframe. // hash within the iframe.
frame?.contentWindow?.postMessage({ hashName: hash }, `${getConfig().LMS_BASE_URL}`); frame.contentWindow.postMessage({ hashName: hash }, `${getConfig().LMS_BASE_URL}`);
} }
}, [id, onLoaded, iframeHeight, hasLoaded]); }, [id, onLoaded, iframeHeight, hasLoaded]);
const receiveMessage = React.useCallback(({ data }: MessageEvent) => { const receiveMessage = React.useCallback(({ data }) => {
const { type, payload } = data; const { type, payload } = data;
if (type === messageTypes.resize) { if (type === messageTypes.resize) {
setIframeHeight(payload.height); setIframeHeight(payload.height);
@@ -80,13 +71,7 @@ const useIFrameBehavior = ({
} else if (data.offset) { } else if (data.offset) {
// We listen for this message from LMS to know when the page needs to // We listen for this message from LMS to know when the page needs to
// be scrolled to another location on the page. // be scrolled to another location on the page.
window.scrollTo(0, data.offset + document.getElementById('unit-iframe')!.offsetTop); window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop);
} else if (type === messageTypes.autoAdvance) {
// We are listening to autoAdvance message to move to next sequence automatically.
// In case it is the last unit we need not do anything.
if (!isLastUnit && nextLink) {
navigate(nextLink);
}
} }
}, [ }, [
id, id,
@@ -102,36 +87,37 @@ const useIFrameBehavior = ({
useEventListener('message', receiveMessage); useEventListener('message', receiveMessage);
// Send visibility status to the iframe. It's used to mark XBlocks as viewed. // Send visibility status to the iframe. It's used to mark XBlocks as viewed.
const updateIframeVisibility = () => {
const iframeElement = document.getElementById(elementId) as HTMLIFrameElement | null;
const rect = iframeElement?.getBoundingClientRect();
const visibleInfo = {
type: 'unit.visibilityStatus',
data: {
topPosition: rect?.top,
viewportHeight: window.innerHeight,
},
};
iframeElement?.contentWindow?.postMessage(
visibleInfo,
`${getConfig().LMS_BASE_URL}`,
);
};
// Set up visibility tracking event listeners.
React.useEffect(() => { React.useEffect(() => {
if (!hasLoaded) { if (!hasLoaded) {
return undefined; return undefined;
} }
const iframeElement = document.getElementById(elementId) as HTMLIFrameElement | null; const iframeElement = document.getElementById(elementId);
if (!iframeElement || !iframeElement.contentWindow) { if (!iframeElement || !iframeElement.contentWindow) {
return undefined; return undefined;
} }
const updateIframeVisibility = () => {
const rect = iframeElement.getBoundingClientRect();
const visibleInfo = {
type: 'unit.visibilityStatus',
data: {
topPosition: rect.top,
viewportHeight: window.innerHeight,
},
};
iframeElement.contentWindow.postMessage(
visibleInfo,
`${getConfig().LMS_BASE_URL}`,
);
};
// Throttle the update function to prevent it from sending too many messages to the iframe. // Throttle the update function to prevent it from sending too many messages to the iframe.
const throttledUpdateVisibility = throttle(updateIframeVisibility, 100); const throttledUpdateVisibility = throttle(updateIframeVisibility, 100);
// Update the visibility of the iframe in case the element is already visible.
updateIframeVisibility();
// Add event listeners to update the visibility of the iframe when the window is scrolled or resized. // Add event listeners to update the visibility of the iframe when the window is scrolled or resized.
window.addEventListener('scroll', throttledUpdateVisibility); window.addEventListener('scroll', throttledUpdateVisibility);
window.addEventListener('resize', throttledUpdateVisibility); window.addEventListener('resize', throttledUpdateVisibility);
@@ -166,9 +152,6 @@ const useIFrameBehavior = ({
dispatch(processEvent(e.data, fetchCourse)); dispatch(processEvent(e.data, fetchCourse));
} }
}; };
// Update the visibility of the iframe in case the element is already visible.
updateIframeVisibility();
}; };
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { renderHook } from '@testing-library/react';
import { getEffects, mockUseKeyedState } from '@edx/react-unit-test-utils';
import { logError } from '@edx/frontend-platform/logging'; import { logError } from '@edx/frontend-platform/logging';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
@@ -8,13 +9,10 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { fetchCourse } from '@src/courseware/data'; import { fetchCourse } from '@src/courseware/data';
import { processEvent } from '@src/course-home/data/thunks'; import { processEvent } from '@src/course-home/data/thunks';
import { useEventListener } from '@src/generic/hooks'; import { useEventListener } from '@src/generic/hooks';
import { useSequenceNavigationMetadata } from '@src/courseware/course/sequence/sequence-navigation/hooks';
import { messageTypes } from '../constants'; import { messageTypes } from '../constants';
import useIFrameBehavior, { iframeBehaviorState } from './useIFrameBehavior'; import useIFrameBehavior, { stateKeys } from './useIFrameBehavior';
const mockNavigate = jest.fn();
jest.mock('@edx/frontend-platform', () => ({ jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(), getConfig: jest.fn(),
@@ -24,14 +22,21 @@ jest.mock('@edx/frontend-platform/analytics');
jest.mock('react', () => ({ jest.mock('react', () => ({
...jest.requireActual('react'), ...jest.requireActual('react'),
useEffect: jest.fn(),
useCallback: jest.fn((cb, prereqs) => ({ cb, prereqs })), useCallback: jest.fn((cb, prereqs) => ({ cb, prereqs })),
})); }));
jest.mock('react-redux', () => ({ jest.mock('react-redux', () => ({
useDispatch: jest.fn(), useDispatch: jest.fn(),
useSelector: jest.fn(),
})); }));
jest.mock('lodash', () => ({
...jest.requireActual('lodash'),
throttle: jest.fn((fn) => fn),
}));
jest.mock('./useLoadBearingHook', () => jest.fn());
jest.mock('@edx/frontend-platform/logging', () => ({ jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(), logError: jest.fn(),
})); }));
@@ -45,16 +50,8 @@ jest.mock('@src/course-home/data/thunks', () => ({
jest.mock('@src/generic/hooks', () => ({ jest.mock('@src/generic/hooks', () => ({
useEventListener: jest.fn(), useEventListener: jest.fn(),
})); }));
jest.mock('@src/generic/model-store', () => ({
useModel: () => ({ unitIds: ['unit1', 'unit2'], entranceExamData: { entranceExamPassed: null } }),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
}));
jest.mock('@src/courseware/course/sequence/sequence-navigation/hooks'); const state = mockUseKeyedState(stateKeys);
useSequenceNavigationMetadata.mockReturnValue({ isLastUnit: false, nextLink: '/next-unit-link' });
const props = { const props = {
elementId: 'test-element-id', elementId: 'test-element-id',
@@ -93,147 +90,148 @@ const stateVals = {
windowTopOffset: 32, windowTopOffset: 32,
}; };
const setIframeHeight = jest.fn();
const setHasLoaded = jest.fn();
const setShowError = jest.fn();
const setWindowTopOffset = jest.fn();
const mockState = (state) => {
const {
iframeHeight, hasLoaded, showError, windowTopOffset,
} = state;
if ('iframeHeight' in state) { jest.spyOn(iframeBehaviorState, 'iframeHeight').mockImplementation(() => [iframeHeight, setIframeHeight]); }
if ('hasLoaded' in state) { jest.spyOn(iframeBehaviorState, 'hasLoaded').mockImplementation(() => [hasLoaded, setHasLoaded]); }
if ('showError' in state) { jest.spyOn(iframeBehaviorState, 'showError').mockImplementation(() => [showError, setShowError]); }
if ('windowTopOffset' in state) { jest.spyOn(iframeBehaviorState, 'windowTopOffset').mockImplementation(() => [windowTopOffset, setWindowTopOffset]); }
};
describe('useIFrameBehavior hook', () => { describe('useIFrameBehavior hook', () => {
let hook;
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
state.mock();
global.document.getElementById = mockGetElementById; global.document.getElementById = mockGetElementById;
global.window.addEventListener = jest.fn(); global.window.addEventListener = jest.fn();
global.window.removeEventListener = jest.fn(); global.window.removeEventListener = jest.fn();
global.window.innerHeight = 800; global.window.innerHeight = 800;
}); });
afterEach(() => {
state.resetVals();
});
describe('behavior', () => { describe('behavior', () => {
it('initializes iframe height to 0 and error/loaded values to false', () => { it('initializes iframe height to 0 and error/loaded values to false', () => {
mockState(defaultStateVals); hook = useIFrameBehavior(props);
const { result } = renderHook(() => useIFrameBehavior(props)); state.expectInitializedWith(stateKeys.iframeHeight, 0);
state.expectInitializedWith(stateKeys.hasLoaded, false);
expect(result.current.iframeHeight).toBe(0); state.expectInitializedWith(stateKeys.showError, false);
expect(result.current.showError).toBe(false); state.expectInitializedWith(stateKeys.windowTopOffset, null);
expect(result.current.hasLoaded).toBe(false);
}); });
describe('effects - on frame change', () => { describe('effects - on frame change', () => {
let oldGetElement; let oldGetElement;
beforeEach(() => { beforeEach(() => {
global.window ??= Object.create(window); global.window ??= Object.create(window);
Object.defineProperty(window, 'location', { value: {}, writable: true }); Object.defineProperty(window, 'location', { value: {}, writable: true });
state.mockVals(stateVals);
oldGetElement = document.getElementById; oldGetElement = document.getElementById;
document.getElementById = mockGetElementById; document.getElementById = mockGetElementById;
mockState(defaultStateVals);
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); state.resetVals();
document.getElementById = oldGetElement; document.getElementById = oldGetElement;
}); });
it('does not post url hash if the window does not have one', () => { it('does not post url hash if the window does not have one', () => {
window.location.hash = ''; hook = useIFrameBehavior(props);
renderHook(() => useIFrameBehavior(props)); const cb = getEffects([
props.id,
props.onLoaded,
testIFrameHeight,
true,
], React)[0];
cb();
expect(postMessage).not.toHaveBeenCalled(); expect(postMessage).not.toHaveBeenCalled();
}); });
it('posts url hash if the window has one', () => { it('posts url hash if the window has one', () => {
window.location.hash = testHash; window.location.hash = testHash;
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const cb = getEffects([
props.id,
props.onLoaded,
testIFrameHeight,
true,
], React)[0];
cb();
expect(postMessage).toHaveBeenCalledWith({ hashName: testHash }, config.LMS_BASE_URL); expect(postMessage).toHaveBeenCalledWith({ hashName: testHash }, config.LMS_BASE_URL);
}); });
}); });
describe('event listener', () => { describe('event listener', () => {
it('calls eventListener with prepared callback', () => { it('calls eventListener with prepared callback', () => {
mockState(stateVals); state.mockVals(stateVals);
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const [call] = useEventListener.mock.calls; const [call] = useEventListener.mock.calls;
expect(call[0]).toEqual('message'); expect(call[0]).toEqual('message');
expect(call[1].prereqs).toEqual([ expect(call[1].prereqs).toEqual([
props.id, props.id,
props.onLoaded, props.onLoaded,
stateVals.hasLoaded, state.values.hasLoaded,
setHasLoaded, state.setState.hasLoaded,
stateVals.iframeHeight, state.values.iframeHeight,
setIframeHeight, state.setState.iframeHeight,
stateVals.windowTopOffset, state.values.windowTopOffset,
setWindowTopOffset, state.setState.windowTopOffset,
]); ]);
}); });
describe('resize message', () => { describe('resize message', () => {
const customHeight = 25; const resizeMessage = (height = 23) => ({
const defaultHeight = 23;
const resizeMessage = (height = defaultHeight) => ({
data: { type: messageTypes.resize, payload: { height } }, data: { type: messageTypes.resize, payload: { height } },
}); });
const videoFullScreenMessage = (open = false) => ({ const videoFullScreenMessage = (open = false) => ({
data: { type: messageTypes.videoFullScreen, payload: { open } }, data: { type: messageTypes.videoFullScreen, payload: { open } },
}); });
const testSetIFrameHeight = (height = defaultHeight) => { const testSetIFrameHeight = (height = 23) => {
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage(height)); cb(resizeMessage(height));
expect(setIframeHeight).toHaveBeenCalledWith(height); expect(state.setState.iframeHeight).toHaveBeenCalledWith(height);
};
const testOnlySetsHeight = () => {
it('sets iframe height with payload height', () => {
testSetIFrameHeight();
});
it('does not set hasLoaded', () => {
expect(state.setState.hasLoaded).not.toHaveBeenCalled();
});
}; };
describe('hasLoaded', () => { describe('hasLoaded', () => {
it('sets iframe height with payload height', () => { beforeEach(() => {
mockState({ ...defaultStateVals, hasLoaded: true }); state.mockVals({ ...defaultStateVals, hasLoaded: true });
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage(customHeight));
expect(setIframeHeight).toHaveBeenCalledWith(0);
expect(setIframeHeight).toHaveBeenCalledWith(customHeight);
expect(setIframeHeight).not.toHaveBeenCalledWith(defaultHeight);
}); });
testOnlySetsHeight();
});
describe('iframeHeight is not 0', () => {
beforeEach(() => {
state.mockVals({ ...defaultStateVals, hasLoaded: true });
hook = useIFrameBehavior(props);
});
testOnlySetsHeight();
}); });
describe('payload height is 0', () => { describe('payload height is 0', () => {
it('sets iframe height with payload height', () => { beforeEach(() => { hook = useIFrameBehavior(props); });
mockState(defaultStateVals); testOnlySetsHeight(0);
renderHook(() => useIFrameBehavior(props));
const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage(0));
expect(setIframeHeight).toHaveBeenCalledWith(0);
expect(setIframeHeight).not.toHaveBeenCalledWith(customHeight);
expect(setIframeHeight).not.toHaveBeenCalledWith(defaultHeight);
});
}); });
describe('payload is present but uninitialized', () => { describe('payload is present but uninitialized', () => {
beforeEach(() => {
mockState(defaultStateVals);
});
it('sets iframe height with payload height', () => { it('sets iframe height with payload height', () => {
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
testSetIFrameHeight(); testSetIFrameHeight();
}); });
it('sets hasLoaded and calls onLoaded', () => { it('sets hasLoaded and calls onLoaded', () => {
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage()); cb(resizeMessage());
expect(setHasLoaded).toHaveBeenCalledWith(true); expect(state.setState.hasLoaded).toHaveBeenCalledWith(true);
expect(props.onLoaded).toHaveBeenCalled(); expect(props.onLoaded).toHaveBeenCalled();
}); });
test('onLoaded is optional', () => { test('onLoaded is optional', () => {
renderHook(() => useIFrameBehavior({ ...props, onLoaded: undefined })); hook = useIFrameBehavior({ ...props, onLoaded: undefined });
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage()); cb(resizeMessage());
expect(setHasLoaded).toHaveBeenCalledWith(true); expect(state.setState.hasLoaded).toHaveBeenCalledWith(true);
}); });
}); });
it('scrolls to current window vertical offset if one is set', () => { it('scrolls to current window vertical offset if one is set', () => {
const windowTopOffset = 32; const windowTopOffset = 32;
mockState({ ...defaultStateVals, windowTopOffset }); state.mockVals({ ...defaultStateVals, windowTopOffset });
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
cb(videoFullScreenMessage()); cb(videoFullScreenMessage());
expect(window.scrollTo).toHaveBeenCalledWith(0, windowTopOffset); expect(window.scrollTo).toHaveBeenCalledWith(0, windowTopOffset);
}); });
it('does not scroll if towverticalp offset is not set', () => { it('does not scroll if towverticalp offset is not set', () => {
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
cb(resizeMessage()); cb(resizeMessage());
expect(window.scrollTo).not.toHaveBeenCalled(); expect(window.scrollTo).not.toHaveBeenCalled();
@@ -247,16 +245,16 @@ describe('useIFrameBehavior hook', () => {
}); });
beforeEach(() => { beforeEach(() => {
window.scrollY = scrollY; window.scrollY = scrollY;
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
[[, { cb }]] = useEventListener.mock.calls; [[, { cb }]] = useEventListener.mock.calls;
}); });
it('sets window top offset based on window.scrollY if opening the video', () => { it('sets window top offset based on window.scrollY if opening the video', () => {
cb(fullScreenMessage(true)); cb(fullScreenMessage(true));
expect(setWindowTopOffset).toHaveBeenCalledWith(scrollY); expect(state.setState.windowTopOffset).toHaveBeenCalledWith(scrollY);
}); });
it('sets window top offset to null if closing the video', () => { it('sets window top offset to null if closing the video', () => {
cb(fullScreenMessage(false)); cb(fullScreenMessage(false));
expect(setWindowTopOffset).toHaveBeenCalledWith(null); expect(state.setState.windowTopOffset).toHaveBeenCalledWith(null);
}); });
}); });
describe('offset message', () => { describe('offset message', () => {
@@ -268,7 +266,7 @@ describe('useIFrameBehavior hook', () => {
document.getElementById = mockGetEl; document.getElementById = mockGetEl;
const oldScrollTo = window.scrollTo; const oldScrollTo = window.scrollTo;
window.scrollTo = jest.fn(); window.scrollTo = jest.fn();
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1]; const { cb } = useEventListener.mock.calls[0][1];
const offset = 99; const offset = 99;
cb({ data: { offset } }); cb({ data: { offset } });
@@ -280,85 +278,18 @@ describe('useIFrameBehavior hook', () => {
}); });
}); });
describe('visibility tracking', () => { describe('visibility tracking', () => {
it('sets up visibility tracking after iframe loads', () => { it('sets up visibility tracking after iframe has loaded', () => {
mockState({ ...defaultStateVals, hasLoaded: true }); state.mockVals({ ...defaultStateVals, hasLoaded: true });
useIFrameBehavior(props);
renderHook(() => useIFrameBehavior(props)); const effects = getEffects([true, props.elementId], React);
expect(effects.length).toEqual(2);
effects[0](); // Execute the visibility tracking effect.
expect(global.window.addEventListener).toHaveBeenCalledTimes(2); expect(global.window.addEventListener).toHaveBeenCalledTimes(2);
expect(global.window.addEventListener).toHaveBeenCalledWith('scroll', expect.any(Function)); expect(global.window.addEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(global.window.addEventListener).toHaveBeenCalledWith('resize', expect.any(Function)); expect(global.window.addEventListener).toHaveBeenCalledWith('resize', expect.any(Function));
// Initial visibility update is handled by the `handleIFrameLoad` method. // Initial visibility update.
expect(postMessage).not.toHaveBeenCalledWith(
expect.objectContaining({ type: 'unit.visibilityStatus' }),
config.LMS_BASE_URL,
);
});
it('does not set up visibility tracking before iframe has loaded', () => {
window.location.hash = ''; // Avoid posting hash message.
mockState({ ...defaultStateVals, hasLoaded: false });
renderHook(() => useIFrameBehavior(props));
expect(global.window.addEventListener).not.toHaveBeenCalled();
expect(postMessage).not.toHaveBeenCalled();
});
it('cleans up event listeners on unmount', () => {
mockState({ ...defaultStateVals, hasLoaded: true });
const { unmount } = renderHook(() => useIFrameBehavior(props));
unmount(); // Call the cleanup function.
expect(global.window.removeEventListener).toHaveBeenCalledTimes(2);
expect(global.window.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(global.window.removeEventListener).toHaveBeenCalledWith('resize', expect.any(Function));
});
});
});
describe('output', () => {
describe('handleIFrameLoad', () => {
it('sets and logs error if has not loaded', () => {
mockState(defaultStateVals);
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
expect(setShowError).toHaveBeenCalledWith(true);
expect(logError).toHaveBeenCalled();
});
it('sends track event if has not loaded', () => {
mockState(defaultStateVals);
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
const eventName = 'edx.bi.error.learning.iframe_load_failed';
const eventProperties = {
unitId: props.id,
iframeUrl: props.iframeUrl,
};
expect(sendTrackEvent).toHaveBeenCalledWith(eventName, eventProperties);
});
it('does not set/log errors if loaded', () => {
mockState({ ...defaultStateVals, hasLoaded: true });
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
expect(setShowError).not.toHaveBeenCalled();
expect(logError).not.toHaveBeenCalled();
});
it('does not send track event if loaded', () => {
mockState({ ...defaultStateVals, hasLoaded: true });
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
expect(sendTrackEvent).not.toHaveBeenCalled();
});
it('registers an event handler to process fetchCourse events.', () => {
mockState(defaultStateVals);
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
const eventName = 'test-event-name';
const event = { data: { event_name: eventName } };
window.onmessage(event);
expect(dispatch).toHaveBeenCalledWith(processEvent(event.data, fetchCourse));
});
it('updates initial iframe visibility on load', () => {
const { result } = renderHook(() => useIFrameBehavior(props));
result.current.handleIFrameLoad();
expect(postMessage).toHaveBeenCalledWith( expect(postMessage).toHaveBeenCalledWith(
{ {
type: 'unit.visibilityStatus', type: 'unit.visibilityStatus',
@@ -370,37 +301,76 @@ describe('useIFrameBehavior hook', () => {
config.LMS_BASE_URL, config.LMS_BASE_URL,
); );
}); });
}); it('does not set up visibility tracking before iframe has loaded', () => {
it('forwards handleIframeLoad, showError, and hasLoaded from state fields', () => { state.mockVals({ ...defaultStateVals, hasLoaded: false });
mockState(stateVals); useIFrameBehavior(props);
const { result } = renderHook(() => useIFrameBehavior(props));
expect(result.current.iframeHeight).toBe(stateVals.iframeHeight); const effects = getEffects([false, props.elementId], React);
expect(result.current.showError).toBe(stateVals.showError); expect(effects).toBeNull();
expect(result.current.hasLoaded).toBe(stateVals.hasLoaded);
expect(global.window.addEventListener).not.toHaveBeenCalled();
expect(postMessage).not.toHaveBeenCalled();
});
it('cleans up event listeners on unmount', () => {
state.mockVals({ ...defaultStateVals, hasLoaded: true });
useIFrameBehavior(props);
const effects = getEffects([true, props.elementId], React);
const cleanup = effects[0](); // Execute the effect and get the cleanup function.
cleanup(); // Call the cleanup function.
expect(global.window.removeEventListener).toHaveBeenCalledTimes(2);
expect(global.window.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
expect(global.window.removeEventListener).toHaveBeenCalledWith('resize', expect.any(Function));
});
}); });
}); });
describe('navigate link for the next unit on auto advance', () => { describe('output', () => {
it('test for link when it is not last unit', () => { describe('handleIFrameLoad', () => {
mockState(defaultStateVals); it('sets and logs error if has not loaded', () => {
renderHook(() => useIFrameBehavior(props)); hook = useIFrameBehavior(props);
const { cb } = useEventListener.mock.calls[0][1]; hook.handleIFrameLoad();
const autoAdvanceMessage = () => ({ expect(state.setState.showError).toHaveBeenCalledWith(true);
data: { type: messageTypes.autoAdvance }, expect(logError).toHaveBeenCalled();
});
it('sends track event if has not loaded', () => {
hook = useIFrameBehavior(props);
hook.handleIFrameLoad();
const eventName = 'edx.bi.error.learning.iframe_load_failed';
const eventProperties = {
unitId: props.id,
iframeUrl: props.iframeUrl,
};
expect(sendTrackEvent).toHaveBeenCalledWith(eventName, eventProperties);
});
it('does not set/log errors if loaded', () => {
state.mockVals({ ...defaultStateVals, hasLoaded: true });
hook = useIFrameBehavior(props);
hook.handleIFrameLoad();
expect(state.setState.showError).not.toHaveBeenCalled();
expect(logError).not.toHaveBeenCalled();
});
it('does not send track event if loaded', () => {
state.mockVals({ ...defaultStateVals, hasLoaded: true });
hook = useIFrameBehavior(props);
hook.handleIFrameLoad();
expect(sendTrackEvent).not.toHaveBeenCalled();
});
it('registers an event handler to process fetchCourse events.', () => {
hook = useIFrameBehavior(props);
hook.handleIFrameLoad();
const eventName = 'test-event-name';
const event = { data: { event_name: eventName } };
window.onmessage(event);
expect(dispatch).toHaveBeenCalledWith(processEvent(event.data, fetchCourse));
}); });
cb(autoAdvanceMessage());
expect(mockNavigate).toHaveBeenCalledWith('/next-unit-link');
}); });
it('test for link when it is last unit', () => { it('forwards handleIframeLoad, showError, and hasLoaded from state fields', () => {
mockState(defaultStateVals); state.mockVals(stateVals);
useSequenceNavigationMetadata.mockReset(); hook = useIFrameBehavior(props);
useSequenceNavigationMetadata.mockReturnValue({ isLastUnit: true, nextLink: '/next-unit-link' }); expect(hook.iframeHeight).toEqual(stateVals.iframeHeight);
renderHook(() => useIFrameBehavior(props)); expect(hook.showError).toEqual(stateVals.showError);
const { cb } = useEventListener.mock.calls[0][1]; expect(hook.hasLoaded).toEqual(stateVals.hasLoaded);
const autoAdvanceMessage = () => ({
data: { type: messageTypes.autoAdvance },
});
cb(autoAdvanceMessage());
expect(mockNavigate).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -1,11 +1,19 @@
import React from 'react'; import React from 'react';
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils/dist';
import { useEventListener } from '@src/generic/hooks'; import { useEventListener } from '@src/generic/hooks';
export const stateKeys = StrictDict({
isOpen: 'isOpen',
options: 'options',
});
export const DEFAULT_HEIGHT = '100%'; export const DEFAULT_HEIGHT = '100%';
const useModalIFrameData = () => { const useModalIFrameData = () => {
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = useKeyedState(stateKeys.isOpen, false);
const [options, setOptions] = React.useState({ height: DEFAULT_HEIGHT }); const [options, setOptions] = useKeyedState(stateKeys.options, { height: DEFAULT_HEIGHT });
const handleModalClose = () => { const handleModalClose = () => {
const rootFrame = document.querySelector('iframe'); const rootFrame = document.querySelector('iframe');

View File

@@ -1,85 +1,74 @@
import React from 'react'; import { mockUseKeyedState } from '@edx/react-unit-test-utils';
import { renderHook } from '@testing-library/react';
import { useEventListener } from '@src/generic/hooks'; import { useEventListener } from '@src/generic/hooks';
import { messageTypes } from '../constants'; import { messageTypes } from '../constants';
import useModalIFrameData, { DEFAULT_HEIGHT } from './useModalIFrameData'; import useModalIFrameData, { stateKeys, DEFAULT_HEIGHT } from './useModalIFrameData';
jest.mock('react', () => ({ jest.mock('react', () => ({
...jest.requireActual('react'), ...jest.requireActual('react'),
useCallback: jest.fn((cb, prereqs) => ({ cb, prereqs })), useCallback: jest.fn((cb, prereqs) => ({ cb, prereqs })),
useState: jest.fn((initialValue) => [initialValue, jest.fn()]),
})); }));
jest.mock('@src/generic/hooks', () => ({ jest.mock('@src/generic/hooks', () => ({
useEventListener: jest.fn(), useEventListener: jest.fn(),
})); }));
const setIsOpen = jest.fn(); const state = mockUseKeyedState(stateKeys);
const setOptions = jest.fn();
const defaultState = {
isOpen: false,
options: { height: DEFAULT_HEIGHT },
};
const mockUseStateWithValues = (values) => {
jest.spyOn(React, 'useState')
.mockReturnValueOnce([values.isOpen, setIsOpen])
.mockReturnValueOnce([values.options, setOptions]);
};
describe('useModalIFrameData', () => { describe('useModalIFrameData', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
state.mock();
}); });
const testHandleModalClose = ({ trigger }) => { const testHandleModalClose = ({ trigger }) => {
const postMessage = jest.fn(); const postMessage = jest.fn();
document.querySelector = jest.fn().mockReturnValue({ contentWindow: { postMessage } }); document.querySelector = jest.fn().mockReturnValue({ contentWindow: { postMessage } });
trigger(); trigger();
expect(React.useState).toHaveBeenNthCalledWith(1, false); state.expectSetStateCalledWith(stateKeys.isOpen, false);
expect(postMessage).toHaveBeenCalledWith({ type: 'plugin.modal-close' }, '*'); expect(postMessage).toHaveBeenCalledWith({ type: 'plugin.modal-close' }, '*');
}; };
describe('behavior', () => { describe('behavior', () => {
it('should initialize with modal closed and default height', () => { it('initializes isOpen to false', () => {
const { result } = renderHook(() => useModalIFrameData()); useModalIFrameData();
state.expectInitializedWith(stateKeys.isOpen, false);
expect(result.current.modalOptions).toEqual({ });
isOpen: false, it('initializes options with default height', () => {
height: DEFAULT_HEIGHT, useModalIFrameData();
}); state.expectInitializedWith(stateKeys.options, { height: DEFAULT_HEIGHT });
}); });
describe('eventListener', () => { describe('eventListener', () => {
const oldOptions = { some: 'old', options: 'yeah' }; const oldOptions = { some: 'old', options: 'yeah' };
const prepareListener = () => { const prepareListener = () => {
useModalIFrameData();
expect(useEventListener).toHaveBeenCalled(); expect(useEventListener).toHaveBeenCalled();
const call = useEventListener.mock.calls[0][1]; const call = useEventListener.mock.calls[0][1];
expect(call.prereqs).toEqual([]); expect(call.prereqs).toEqual([]);
return call.cb; return call.cb;
}; };
it('consumes modal events and opens sets modal options with open: true', () => { it('consumes modal events and opens sets modal options with open: true', () => {
mockUseStateWithValues({ state.mockVals({
isOpen: false, [stateKeys.isOpen]: false,
options: oldOptions, [stateKeys.options]: oldOptions,
}); });
renderHook(() => useModalIFrameData());
const receiveMessage = prepareListener(); const receiveMessage = prepareListener();
const payload = { test: 'values' }; const payload = { test: 'values' };
receiveMessage({ data: { type: messageTypes.modal, payload } }); receiveMessage({ data: { type: messageTypes.modal, payload } });
expect(setIsOpen).toHaveBeenCalledWith(true); expect(state.setState.isOpen).toHaveBeenCalledWith(true);
expect(setOptions).toHaveBeenCalled(); expect(state.setState.options).toHaveBeenCalled();
const [[setOptionsCb]] = setOptions.mock.calls; const [[setOptionsCb]] = state.setState.options.mock.calls;
expect(setOptionsCb(oldOptions)).toEqual({ ...oldOptions, ...payload }); expect(setOptionsCb(oldOptions)).toEqual({ ...oldOptions, ...payload });
}); });
it('ignores events with no type', () => { it('ignores events with no type', () => {
const { result } = renderHook(() => useModalIFrameData()); state.mockVals({
const initialState = result.current.modalOptions; [stateKeys.isOpen]: false,
[stateKeys.options]: oldOptions,
});
const receiveMessage = prepareListener(); const receiveMessage = prepareListener();
const payload = { test: 'values' }; const payload = { test: 'values' };
receiveMessage({ data: { payload } }); receiveMessage({ data: { payload } });
expect(result.current.modalOptions).toEqual(initialState); expect(state.setState.isOpen).not.toHaveBeenCalled();
expect(state.setState.options).not.toHaveBeenCalled();
}); });
it('calls handleModalClose behavior when receiving a "plugin.modal-close" event', () => { it('calls handleModalClose behavior when receiving a "plugin.modal-close" event', () => {
renderHook(() => useModalIFrameData());
const receiveMessage = prepareListener(); const receiveMessage = prepareListener();
testHandleModalClose({ testHandleModalClose({
trigger: () => { trigger: () => {
@@ -91,14 +80,13 @@ describe('useModalIFrameData', () => {
}); });
describe('output', () => { describe('output', () => {
test('returns handleModalClose callback', () => { test('returns handleModalClose callback', () => {
mockUseStateWithValues(defaultState);
testHandleModalClose({ trigger: useModalIFrameData().handleModalClose }); testHandleModalClose({ trigger: useModalIFrameData().handleModalClose });
}); });
it('forwards modalOptions from state values', () => { it('forwards modalOptions from state values', () => {
const modalOptions = { test: 'options' }; const modalOptions = { test: 'options' };
mockUseStateWithValues({ state.mockVals({
isOpen: true, [stateKeys.options]: modalOptions,
options: modalOptions, [stateKeys.isOpen]: true,
}); });
expect(useModalIFrameData().modalOptions).toEqual({ expect(useModalIFrameData().modalOptions).toEqual({
...modalOptions, ...modalOptions,

View File

@@ -1,13 +1,19 @@
import React from 'react'; import React from 'react';
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils/dist';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import { modelKeys } from '../constants'; import { modelKeys } from '../constants';
export const stateKeys = StrictDict({
shouldDisplay: 'shouldDisplay',
});
/** /**
* @return {bool} should the honor code be displayed? * @return {bool} should the honor code be displayed?
*/ */
const useShouldDisplayHonorCode = ({ id, courseId }) => { const useShouldDisplayHonorCode = ({ id, courseId }) => {
const [shouldDisplay, setShouldDisplay] = React.useState(false); const [shouldDisplay, setShouldDisplay] = useKeyedState(stateKeys.shouldDisplay, false);
const { graded } = useModel(modelKeys.units, id); const { graded } = useModel(modelKeys.units, id);
const { userNeedsIntegritySignature } = useModel(modelKeys.coursewareMeta, courseId); const { userNeedsIntegritySignature } = useModel(modelKeys.coursewareMeta, courseId);

View File

@@ -1,12 +1,22 @@
import { renderHook } from '@testing-library/react'; import React from 'react';
import { getEffects, mockUseKeyedState } from '@edx/react-unit-test-utils';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import useShouldDisplayHonorCode from './useShouldDisplayHonorCode';
import { modelKeys } from '../constants'; import { modelKeys } from '../constants';
import useShouldDisplayHonorCode, { stateKeys } from './useShouldDisplayHonorCode';
jest.mock('react', () => ({
...jest.requireActual('react'),
useEffect: jest.fn(),
}));
jest.mock('@src/generic/model-store', () => ({ jest.mock('@src/generic/model-store', () => ({
useModel: jest.fn(), useModel: jest.fn(),
})); }));
const state = mockUseKeyedState(stateKeys);
const props = { const props = {
id: 'test-id', id: 'test-id',
courseId: 'test-course-id', courseId: 'test-course-id',
@@ -18,29 +28,52 @@ const mockModels = (graded, userNeedsIntegritySignature) => {
)); ));
}; };
describe('useShouldDisplayHonorCode', () => { describe('useShouldDisplayHonorCode hook', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
mockModels(false, false);
state.mock();
}); });
describe('behavior', () => {
it('should return false when userNeedsIntegritySignature is false', () => { it('initializes shouldDisplay to false', () => {
mockModels(true, false); useShouldDisplayHonorCode(props);
state.expectInitializedWith(stateKeys.shouldDisplay, false);
const { result } = renderHook(() => useShouldDisplayHonorCode(props)); });
expect(result.current).toBe(false); describe('effect - on userNeedsIntegritySignature', () => {
describe('graded and needs integrity signature', () => {
it('sets shouldDisplay(true)', () => {
mockModels(true, true);
useShouldDisplayHonorCode(props);
const cb = getEffects([state.setState.shouldDisplay, true], React)[0];
cb();
expect(state.setState.shouldDisplay).toHaveBeenCalledWith(true);
});
});
describe('not graded', () => {
it('sets should not display', () => {
mockModels(true, false);
useShouldDisplayHonorCode(props);
const cb = getEffects([state.setState.shouldDisplay, false], React)[0];
cb();
expect(state.setState.shouldDisplay).toHaveBeenCalledWith(false);
});
});
describe('does not need integrity signature', () => {
it('sets should not display', () => {
mockModels(false, true);
useShouldDisplayHonorCode(props);
const cb = getEffects([state.setState.shouldDisplay, true], React)[0];
cb();
expect(state.setState.shouldDisplay).toHaveBeenCalledWith(false);
});
});
});
}); });
describe('output', () => {
it('should return false when graded is false', () => { it('returns shouldDisplay value from state', () => {
mockModels(false, true); const testValue = 'test-value';
state.mockVal(stateKeys.shouldDisplay, testValue);
const { result } = renderHook(() => useShouldDisplayHonorCode(props)); expect(useShouldDisplayHonorCode(props)).toEqual(testValue);
expect(result.current).toBe(false); });
});
it('should return true when both userNeedsIntegritySignature and graded are true', () => {
mockModels(true, true);
const { result } = renderHook(() => useShouldDisplayHonorCode(props));
expect(result.current).toBe(true);
}); });
}); });

View File

@@ -22,6 +22,7 @@ const Unit = ({
onLoaded, onLoaded,
id, id,
isOriginalUserStaff, isOriginalUserStaff,
isEnabledOutlineSidebar,
renderUnitNavigation, renderUnitNavigation,
}) => { }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -47,7 +48,7 @@ const Unit = ({
return ( return (
<div className="unit"> <div className="unit">
<UnitTitleSlot unitId={id} {...{ unit, renderUnitNavigation }} /> <UnitTitleSlot unitId={id} {...{ unit, isEnabledOutlineSidebar, renderUnitNavigation }} />
<UnitSuspense {...{ courseId, id }} /> <UnitSuspense {...{ courseId, id }} />
<ContentIFrame <ContentIFrame
elementId="unit-iframe" elementId="unit-iframe"
@@ -69,6 +70,7 @@ Unit.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
onLoaded: PropTypes.func, onLoaded: PropTypes.func,
isOriginalUserStaff: PropTypes.bool.isRequired, isOriginalUserStaff: PropTypes.bool.isRequired,
isEnabledOutlineSidebar: PropTypes.bool.isRequired,
renderUnitNavigation: PropTypes.func.isRequired, renderUnitNavigation: PropTypes.func.isRequired,
}; };

View File

@@ -14,6 +14,7 @@ const defaultProps = {
onLoaded: jest.fn().mockName('props.onLoaded'), onLoaded: jest.fn().mockName('props.onLoaded'),
id: 'unit-id', id: 'unit-id',
isOriginalUserStaff: false, isOriginalUserStaff: false,
isEnabledOutlineSidebar: false,
renderUnitNavigation: jest.fn(enabled => enabled && 'UnitNaviagtion'), renderUnitNavigation: jest.fn(enabled => enabled && 'UnitNaviagtion'),
}; };
@@ -67,8 +68,16 @@ describe('<Unit />', () => {
expect(screen.getByText('Bookmark this page')).toBeInTheDocument(); expect(screen.getByText('Bookmark this page')).toBeInTheDocument();
}); });
it('renders unit navigation buttons', () => { it('does not render unit navigation buttons', () => {
const props = { ...defaultProps }; renderComponent(defaultProps);
const nextButton = screen.queryByText('UnitNaviagtion');
expect(nextButton).toBeNull();
});
it('renders unit navigation buttons when isEnabledOutlineSidebar is true', () => {
const props = { ...defaultProps, isEnabledOutlineSidebar: true };
renderComponent(props); renderComponent(props);
const nextButton = screen.getByText('UnitNaviagtion'); const nextButton = screen.getByText('UnitNaviagtion');

View File

@@ -0,0 +1,35 @@
import { getConfig } from '@edx/frontend-platform';
import { stringifyUrl } from 'query-string';
export const iframeParams = {
show_title: 0,
show_bookmark: 0,
recheck_access: 1,
};
export const getIFrameUrl = ({
id,
view,
format,
examAccess,
jumpToId,
preview,
}) => {
const xblockUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}`;
return stringifyUrl({
url: xblockUrl,
query: {
...iframeParams,
view,
preview,
...(format && { format }),
...(!examAccess.blockAccess && { exam_access: examAccess.accessToken }),
jumpToId, // Pass jumpToId as query param as fragmentIdentifier is not passed to server.
},
fragmentIdentifier: jumpToId, // this is used by browser to scroll to correct block.
});
};
export default {
getIFrameUrl,
};

View File

@@ -0,0 +1,83 @@
import { getConfig } from '@edx/frontend-platform';
import { stringifyUrl } from 'query-string';
import { getIFrameUrl, iframeParams } from './urls';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));
jest.mock('query-string', () => ({
stringifyUrl: jest.fn((arg) => ({ stringifyUrl: arg })),
}));
const config = { LMS_BASE_URL: 'test-lms-url' };
getConfig.mockReturnValue(config);
const props = {
id: 'test-id',
view: 'test-view',
format: 'test-format',
examAccess: { blockAccess: false, accessToken: 'test-access-token' },
preview: false,
};
describe('urls module getIFrameUrl', () => {
test('format provided, exam access and token available', () => {
const url = stringifyUrl({
url: `${config.LMS_BASE_URL}/xblock/${props.id}`,
query: {
...iframeParams,
view: props.view,
format: props.format,
exam_access: props.examAccess.accessToken,
preview: props.preview,
},
});
expect(getIFrameUrl(props)).toEqual(url);
});
test('no format provided, exam access blocked', () => {
const url = stringifyUrl({
url: `${config.LMS_BASE_URL}/xblock/${props.id}`,
query: { ...iframeParams, view: props.view, preview: props.preview },
});
expect(getIFrameUrl({
id: props.id,
view: props.view,
preview: props.preview,
examAccess: { blockAccess: true },
})).toEqual(url);
});
test('jumpToId and fragmentIdentifier is added to url', () => {
const url = stringifyUrl({
url: `${config.LMS_BASE_URL}/xblock/${props.id}`,
query: {
...iframeParams,
view: props.view,
format: props.format,
preview: props.preview,
exam_access: props.examAccess.accessToken,
jumpToId: 'some-xblock-id',
},
fragmentIdentifier: 'some-xblock-id',
});
expect(getIFrameUrl({
...props,
jumpToId: 'some-xblock-id',
})).toEqual(url);
});
test('preview is true and url param equals 1', () => {
const url = stringifyUrl({
url: `${config.LMS_BASE_URL}/xblock/${props.id}`,
query: {
...iframeParams,
view: props.view,
format: props.format,
preview: true,
exam_access: props.examAccess.accessToken,
},
});
expect(getIFrameUrl({
...props,
preview: true,
})).toEqual(url);
});
});

View File

@@ -1,42 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
import { getIFrameUrl } from './urls';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));
const config = { LMS_BASE_URL: 'https://test-lms-url' };
getConfig.mockReturnValue(config);
const props = {
id: 'test-id',
view: 'test-view',
format: 'test-format',
examAccess: { blockAccess: false, accessToken: 'test-access-token' },
preview: false,
};
describe('urls module getIFrameUrl', () => {
test('format provided, exam access and token available', () => {
expect(getIFrameUrl(props)).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view');
});
test('no format provided, exam access blocked', () => {
expect(getIFrameUrl({
id: props.id,
view: props.view,
preview: props.preview,
examAccess: { blockAccess: true },
})).toEqual('https://test-lms-url/xblock/test-id?preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view');
});
test('jumpToId and fragmentIdentifier is added to url', () => {
expect(getIFrameUrl({
...props,
jumpToId: 'some-xblock-id',
})).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&jumpToId=some-xblock-id&preview=false&recheck_access=1&show_bookmark=0&show_title=0&view=test-view#some-xblock-id');
});
test('preview is true and url param equals 1', () => {
expect(getIFrameUrl({
...props,
preview: true,
})).toEqual('https://test-lms-url/xblock/test-id?exam_access=test-access-token&format=test-format&preview=true&recheck_access=1&show_bookmark=0&show_title=0&view=test-view');
});
});

View File

@@ -1,49 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
export const iframeParams = {
show_title: 0,
show_bookmark: 0,
recheck_access: 1,
};
interface Props {
id: string;
view: string;
format?: string | null;
examAccess: { blockAccess: boolean, accessToken?: string };
jumpToId?: string;
preview: boolean;
}
export const getIFrameUrl = ({
id,
view,
format = null,
examAccess,
jumpToId,
preview,
}: Props) => {
const xblockUrl = new URL(`${getConfig().LMS_BASE_URL}/xblock/${id}`);
for (const [key, value] of Object.entries(iframeParams)) {
xblockUrl.searchParams.set(key, String(value));
}
xblockUrl.searchParams.set('view', view);
xblockUrl.searchParams.set('preview', String(preview));
if (format) {
xblockUrl.searchParams.set('format', format);
}
if (!examAccess.blockAccess) {
xblockUrl.searchParams.set('exam_access', examAccess.accessToken!);
}
// Pass jumpToId as query param as fragmentIdentifier is not passed to server.
if (jumpToId) {
xblockUrl.searchParams.set('jumpToId', jumpToId);
xblockUrl.hash = `#${jumpToId}`; // this is used by browser to scroll to correct block.
}
xblockUrl.searchParams.sort();
return xblockUrl.toString();
};
export default {
getIFrameUrl,
};

View File

@@ -4,7 +4,7 @@
} }
.lock-paywall-container svg { .lock-paywall-container svg {
color: var(--pgn-color-primary-700); color: $primary-700;
} }
@media only screen and (min-width: 992px) and (max-width: 1100px) { @media only screen and (min-width: 992px) and (max-width: 1100px) {

View File

@@ -100,13 +100,13 @@ const SequenceNavigation = ({
); );
}; };
return sequenceStatus === LOADED ? ( return sequenceStatus === LOADED && (
<nav id="courseware-sequence-navigation" data-testid="courseware-sequence-navigation" className={classNames('sequence-navigation', className, { 'mr-2': shouldDisplayNotificationTriggerInSequence })}> <nav id="courseware-sequence-navigation" data-testid="courseware-sequence-navigation" className={classNames('sequence-navigation', className, { 'mr-2': shouldDisplayNotificationTriggerInSequence })}>
{renderPreviousButton()} {renderPreviousButton()}
{renderUnitButtons()} {renderUnitButtons()}
{renderNextButton()} {renderNextButton()}
</nav> </nav>
) : null; );
}; };
SequenceNavigation.propTypes = { SequenceNavigation.propTypes = {

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { PropTypes } from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { TwitterShareButton, TwitterIcon } from 'react-share';
import { stringifyUrl } from 'query-string';
import { Icon } from '@openedx/paragon';
import messages from './messages';
const ShareTwitterIcon = () => (
<TwitterIcon
round
iconFillColor="#0A3055"
bgStyle={{
fill: '#fff',
}}
/>
);
const ShareButton = ({ url }) => {
const { formatMessage } = useIntl();
const twitterUrl = stringifyUrl({
url,
query: {
utm_source: 'twitter',
utm_medium: 'social',
utm_campaign: 'social-share-exp',
},
});
return (
<TwitterShareButton
url={twitterUrl}
title={formatMessage(messages.shareQuote)}
resetButtonStyle={false}
className="px-1 ml-n1 btn-sm text-primary-500 btn btn-link"
>
<Icon src={ShareTwitterIcon} />
{formatMessage(messages.shareButton)}
</TwitterShareButton>
);
};
ShareButton.propTypes = {
url: PropTypes.string.isRequired,
};
export default ShareButton;

View File

@@ -0,0 +1,26 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
shareButton: {
id: 'learn.sequence.share.button',
defaultMessage: 'Share this content',
description: 'share message button message',
},
shareModalTitle: {
id: 'learn.sequence.share.modal.title',
defaultMessage: 'Title',
description: 'share message modal title',
},
shareModalBody: {
id: 'learn.sequence.share.modal.body',
defaultMessage: 'Copy the link below to share this content.',
description: 'share message modal body',
},
shareQuote: {
id: 'learn.sequence.share.quote',
defaultMessage: 'Here\'s a fun clip from a class I\'m taking on @edXonline.\n',
description: 'share message quote',
},
});
export default messages;

View File

@@ -1,11 +1,13 @@
import { breakpoints, useWindowSize } from '@openedx/paragon'; import { breakpoints, useWindowSize } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { import {
useEffect, useState, useMemo, useCallback, useEffect, useState, useMemo, useCallback,
} from 'react'; } from 'react';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import { getLocalStorage, setLocalStorage } from '@src/data/localStorage'; import { getLocalStorage, setLocalStorage } from '@src/data/localStorage';
import { getCoursewareOutlineSidebarSettings } from '../../data/selectors';
import * as discussionsSidebar from './sidebars/discussions'; import * as discussionsSidebar from './sidebars/discussions';
import * as notificationsSidebar from './sidebars/notifications'; import * as notificationsSidebar from './sidebars/notifications';
@@ -23,10 +25,11 @@ const SidebarProvider = ({
const shouldDisplayFullScreen = useWindowSize().width < breakpoints.extraLarge.minWidth; const shouldDisplayFullScreen = useWindowSize().width < breakpoints.extraLarge.minWidth;
const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.extraLarge.minWidth; const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.extraLarge.minWidth;
const query = new URLSearchParams(window.location.search); const query = new URLSearchParams(window.location.search);
const { alwaysOpenAuxiliarySidebar } = useSelector(getCoursewareOutlineSidebarSettings);
const isInitiallySidebarOpen = shouldDisplaySidebarOpen || query.get('sidebar') === 'true'; const isInitiallySidebarOpen = shouldDisplaySidebarOpen || query.get('sidebar') === 'true';
let initialSidebar = shouldDisplayFullScreen ? getLocalStorage(`sidebar.${courseId}`) : null; let initialSidebar = shouldDisplayFullScreen ? getLocalStorage(`sidebar.${courseId}`) : null;
if (!shouldDisplayFullScreen && isInitiallySidebarOpen) { if (!shouldDisplayFullScreen && isInitiallySidebarOpen && alwaysOpenAuxiliarySidebar) {
initialSidebar = isUnitHasDiscussionTopics initialSidebar = isUnitHasDiscussionTopics
? SIDEBARS[discussionsSidebar.ID].ID ? SIDEBARS[discussionsSidebar.ID].ID
: verifiedMode && SIDEBARS[notificationsSidebar.ID].ID; : verifiedMode && SIDEBARS[notificationsSidebar.ID].ID;

View File

@@ -1,5 +1,5 @@
#course-sidebar { #course-sidebar {
@media (--pgn-size-breakpoint-max-width-lg) { @media (max-width: -1 + map-get($grid-breakpoints, "lg")) {
overflow-y: scroll; overflow-y: scroll;
padding: 0 .625rem !important; padding: 0 .625rem !important;
} }

View File

@@ -7,6 +7,7 @@ import {
ChevronLeft as ChevronLeftIcon, ChevronLeft as ChevronLeftIcon,
} from '@openedx/paragon/icons'; } from '@openedx/paragon/icons';
import { useModel } from '@src/generic/model-store';
import { LOADING } from '@src/constants'; import { LOADING } from '@src/constants';
import PageLoading from '@src/generic/PageLoading'; import PageLoading from '@src/generic/PageLoading';
import SidebarSection from './components/SidebarSection'; import SidebarSection from './components/SidebarSection';
@@ -23,6 +24,7 @@ const CourseOutlineTray = () => {
const { const {
courseId, courseId,
unitId, unitId,
isEnabledSidebar,
currentSidebar, currentSidebar,
handleToggleCollapse, handleToggleCollapse,
isActiveEntranceExam, isActiveEntranceExam,
@@ -33,13 +35,13 @@ const CourseOutlineTray = () => {
sequences, sequences,
} = useCourseOutlineSidebar(); } = useCourseOutlineSidebar();
const resolvedSectionId = selectedSection const {
|| Object.keys(sections).find( sectionId: activeSectionId,
(sectionId) => sections[sectionId].sequenceIds.includes(activeSequenceId), } = useModel('sequences', activeSequenceId);
);
const sectionsIds = Object.keys(sections); const sectionsIds = Object.keys(sections);
const sequenceIds = sections[resolvedSectionId]?.sequenceIds || []; const sequenceIds = sections[selectedSection || activeSectionId]?.sequenceIds || [];
const backButtonTitle = sections[resolvedSectionId]?.title; const backButtonTitle = sections[selectedSection || activeSectionId]?.title;
const handleBackToSectionLevel = () => { const handleBackToSectionLevel = () => {
setDisplaySectionLevel(); setDisplaySectionLevel();
@@ -76,7 +78,7 @@ const CourseOutlineTray = () => {
</div> </div>
); );
if (isActiveEntranceExam || currentSidebar !== ID) { if (!isEnabledSidebar || isActiveEntranceExam || currentSidebar !== ID) {
return null; return null;
} }

View File

@@ -1,12 +1,14 @@
.outline-sidebar-wrapper { .outline-sidebar-wrapper {
width: 32.125rem; width: 32.125rem;
max-width: 100%; max-width: 100%;
overflow: auto;
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
} }
.outline-sidebar { .outline-sidebar {
@media (--pgn-size-breakpoint-min-width-xl) { @media (min-width: map-get($grid-breakpoints, "xl")) {
position: absolute;
left: 0; left: 0;
top: 0; top: 0;
} }
@@ -23,12 +25,12 @@
} }
.outline-sidebar-heading { .outline-sidebar-heading {
font-weight: var(--pgn-typography-font-weight-bold); font-weight: $font-weight-bold;
} }
} }
.course-sidebar-section { .course-sidebar-section {
background: var(--pgn-color-white); background: $white;
border: 1px solid #d7d3d1; border: 1px solid #d7d3d1;
button { button {
@@ -52,7 +54,7 @@
#outline-sidebar-outline { #outline-sidebar-outline {
margin-top: -1px; margin-top: -1px;
@media (--pgn-size-breakpoint-min-width-xl) { @media (min-width: map-get($grid-breakpoints, "xl")) {
margin-bottom: 0; margin-bottom: 0;
} }
@@ -62,14 +64,14 @@
.collapsible-trigger { .collapsible-trigger {
border-radius: 0; border-radius: 0;
padding: var(--pgn-spacing-spacer-3-5) var(--pgn-spacing-spacer-4) var(--pgn-spacing-spacer-3-5) var(--pgn-spacing-spacer-5); padding: map-get($spacers, 3\.5) map-get($spacers, 4) map-get($spacers, 3\.5) map-get($spacers, 5);
@media (--pgn-size-breakpoint-max-width-sm) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
padding-left: var(--pgn-spacing-spacer-4); padding-left: map-get($spacers, 4);
} }
&:hover { &:hover {
background-color: var(--pgn-color-light-500); background-color: $light-500;
} }
.collapsible-icon { .collapsible-icon {
@@ -78,7 +80,7 @@
} }
&:last-child .pgn_collapsible { &:last-child .pgn_collapsible {
margin-bottom: 0px !important; @extend .mb-0;
} }
} }
@@ -86,15 +88,15 @@
padding: 0; padding: 0;
ol li > a { ol li > a {
padding: var(--pgn-spacing-spacer-3-5) var(--pgn-spacing-spacer-4) var(--pgn-spacing-spacer-3-5) var(--pgn-spacing-spacer-5-5); padding: map-get($spacers, 3\.5) map-get($spacers, 4) map-get($spacers, 3\.5) map-get($spacers, 5\.5);
@media (--pgn-size-breakpoint-max-width-sm) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
padding-left: var(--pgn-spacing-spacer-4-5); padding-left: map-get($spacers, 4\.5);
} }
&:hover { &:hover {
text-decoration: none; text-decoration: none;
background-color: var(--pgn-color-light-500); background-color: $light-500;
} }
} }
} }

View File

@@ -67,6 +67,15 @@ describe('<CourseOutlineTray />', () => {
expect(screen.queryByRole('button', { name: 'Course outline' })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Course outline' })).not.toBeInTheDocument();
}); });
it('doesn\'t render when outline sidebar is disabled', async () => {
await initTestStore({ enableNavigationSidebar: { enable_navigation_sidebar: false } });
renderWithProvider();
await expect(screen.queryByText(messages.loading.defaultMessage)).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: section.title })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: messages.toggleCourseOutlineTrigger.defaultMessage })).not.toBeInTheDocument();
});
it('renders correctly when course outline is loaded', async () => { it('renders correctly when course outline is loaded', async () => {
await initTestStore(); await initTestStore();
renderWithProvider(); renderWithProvider();
@@ -93,21 +102,6 @@ describe('<CourseOutlineTray />', () => {
expect(mockToggleSidebar).toHaveBeenCalledWith(null); expect(mockToggleSidebar).toHaveBeenCalledWith(null);
}); });
it('collapses sidebar correctly when screen is resized', async () => {
const mockToggleSidebar = jest.fn();
await initTestStore();
renderWithProvider({ toggleSidebar: mockToggleSidebar });
const collapseBtn = screen.getByRole('button', { name: messages.toggleCourseOutlineTrigger.defaultMessage });
expect(collapseBtn).toBeInTheDocument();
// Simulate screen resize
window.innerWidth = 500;
window.dispatchEvent(new Event('resize'));
expect(mockToggleSidebar).toHaveBeenCalledWith(null);
});
it('navigates to section or sequence level correctly on click by back/section button', async () => { it('navigates to section or sequence level correctly on click by back/section button', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
await initTestStore(); await initTestStore();

View File

@@ -15,12 +15,13 @@ const CourseOutlineTrigger = ({ isMobileView }) => {
shouldDisplayFullScreen, shouldDisplayFullScreen,
handleToggleCollapse, handleToggleCollapse,
isActiveEntranceExam, isActiveEntranceExam,
isEnabledSidebar,
} = useCourseOutlineSidebar(); } = useCourseOutlineSidebar();
const isDisplayForDesktopView = !isMobileView && !shouldDisplayFullScreen && currentSidebar !== ID; const isDisplayForDesktopView = !isMobileView && !shouldDisplayFullScreen && currentSidebar !== ID;
const isDisplayForMobileView = isMobileView && shouldDisplayFullScreen; const isDisplayForMobileView = isMobileView && shouldDisplayFullScreen;
if ((!isDisplayForDesktopView && !isDisplayForMobileView) || isActiveEntranceExam) { if ((!isDisplayForDesktopView && !isDisplayForMobileView) || !isEnabledSidebar || isActiveEntranceExam) {
return null; return null;
} }

View File

@@ -45,7 +45,7 @@ describe('<CourseOutlineTrigger />', () => {
it('renders correctly for desktop when sidebar is enabled', async () => { it('renders correctly for desktop when sidebar is enabled', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const mockToggleSidebar = jest.fn(); const mockToggleSidebar = jest.fn();
await initTestStore(); await initTestStore({ enableNavigationSidebar: { enable_navigation_sidebar: true } });
renderWithProvider({ toggleSidebar: mockToggleSidebar }, { isMobileView: false }); renderWithProvider({ toggleSidebar: mockToggleSidebar }, { isMobileView: false });
const toggleButton = await screen.getByRole('button', { const toggleButton = await screen.getByRole('button', {
@@ -62,7 +62,7 @@ describe('<CourseOutlineTrigger />', () => {
it('renders correctly for mobile when sidebar is enabled', async () => { it('renders correctly for mobile when sidebar is enabled', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const mockToggleSidebar = jest.fn(); const mockToggleSidebar = jest.fn();
await initTestStore(); await initTestStore({ enableNavigationSidebar: { enable_navigation_sidebar: true } });
renderWithProvider({ renderWithProvider({
toggleSidebar: mockToggleSidebar, toggleSidebar: mockToggleSidebar,
shouldDisplayFullScreen: true, shouldDisplayFullScreen: true,
@@ -82,7 +82,7 @@ describe('<CourseOutlineTrigger />', () => {
it('changes current sidebar value on click', async () => { it('changes current sidebar value on click', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const mockToggleSidebar = jest.fn(); const mockToggleSidebar = jest.fn();
await initTestStore(); await initTestStore({ enableNavigationSidebar: { enable_navigation_sidebar: true } });
renderWithProvider({ renderWithProvider({
toggleSidebar: mockToggleSidebar, toggleSidebar: mockToggleSidebar,
shouldDisplayFullScreen: true, shouldDisplayFullScreen: true,
@@ -99,4 +99,14 @@ describe('<CourseOutlineTrigger />', () => {
expect(mockToggleSidebar).toHaveBeenCalledTimes(1); expect(mockToggleSidebar).toHaveBeenCalledTimes(1);
expect(mockToggleSidebar).toHaveBeenCalledWith(null); expect(mockToggleSidebar).toHaveBeenCalledWith(null);
}); });
it('does not render when isEnabled is false', async () => {
await initTestStore({ enableNavigationSidebar: { enable_navigation_sidebar: false } });
renderWithProvider({}, { isMobileView: false });
const toggleButton = await screen.queryByRole('button', {
name: messages.toggleCourseOutlineTrigger.defaultMessage,
});
expect(toggleButton).not.toBeInTheDocument();
});
}); });

View File

@@ -6,12 +6,12 @@ import {
import { DashedCircleIcon } from '../icons'; import { DashedCircleIcon } from '../icons';
const CompletionIcon = ({ completionStat: { completed = 0, total = 0 }, enabled }) => { const CompletionIcon = ({ completionStat: { completed = 0, total = 0 } }) => {
const percentage = total !== 0 ? Math.min((completed / total) * 100, 100) : 0; const percentage = total !== 0 ? Math.min((completed / total) * 100, 100) : 0;
const remainder = 100 - percentage; const remainder = 100 - percentage;
switch (true) { switch (true) {
case !completed || !enabled: case !completed:
return <LmsCompletionSolidIcon className="text-gray-300" data-testid="completion-solid-icon" />; return <LmsCompletionSolidIcon className="text-gray-300" data-testid="completion-solid-icon" />;
case completed === total: case completed === total:
return <CheckCircleIcon className="text-success" data-testid="check-circle-icon" />; return <CheckCircleIcon className="text-success" data-testid="check-circle-icon" />;
@@ -25,7 +25,6 @@ CompletionIcon.propTypes = {
completed: PropTypes.number, completed: PropTypes.number,
total: PropTypes.number, total: PropTypes.number,
}).isRequired, }).isRequired,
enabled: PropTypes.bool.isRequired,
}; };
export default CompletionIcon; export default CompletionIcon;

View File

@@ -3,33 +3,21 @@ import { render, screen } from '@testing-library/react';
import CompletionIcon from './CompletionIcon'; import CompletionIcon from './CompletionIcon';
describe('CompletionIcon', () => { describe('CompletionIcon', () => {
it('renders check circle icon when completion is equal to total and completion tracking is enabled', () => { it('renders check circle icon when completion is equal to total', () => {
const completionStat = { completed: 5, total: 5 }; const completionStat = { completed: 5, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled />); render(<CompletionIcon completionStat={completionStat} />);
expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument(); expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument();
}); });
it('renders dashed circle icon when completion is between 0 and total and completion tracking is enabled', () => { it('renders dashed circle icon when completion is between 0 and total', () => {
const completionStat = { completed: 2, total: 5 }; const completionStat = { completed: 2, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled />); render(<CompletionIcon completionStat={completionStat} />);
expect(screen.getByTestId('dashed-circle-icon')).toBeInTheDocument(); expect(screen.getByTestId('dashed-circle-icon')).toBeInTheDocument();
}); });
it('renders completion solid icon when completion is between 0 and total and completion tracking is not enabled', () => { it('renders completion solid icon when completion is 0', () => {
const completionStat = { completed: 2, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled={false} />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
});
it('renders completion solid icon when completion is 0 and enabled', () => {
const completionStat = { completed: 0, total: 5 }; const completionStat = { completed: 0, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled />); render(<CompletionIcon completionStat={completionStat} />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
});
it('renders completion solid icon when completion is at any value and not enabled', () => {
const completionStat = { completed: 0, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled={false} />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument(); expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
}); });
}); });

View File

@@ -18,24 +18,21 @@ const SidebarSection = ({ section, handleSelectSection }) => {
completionStat, completionStat,
} = section; } = section;
const { activeSequenceId, isEnabledCompletionTracking } = useCourseOutlineSidebar(); const { activeSequenceId } = useCourseOutlineSidebar();
const isActiveSection = sequenceIds.includes(activeSequenceId); const isActiveSection = sequenceIds.includes(activeSequenceId);
const sectionTitle = ( const sectionTitle = (
<> <>
<div className="col-auto p-0"> <div className="col-auto p-0">
<CompletionIcon completionStat={completionStat} enabled={isEnabledCompletionTracking} /> <CompletionIcon completionStat={completionStat} />
</div> </div>
<div className="col-10 ml-3 p-0 flex-grow-1 text-dark-500 text-left text-break"> <div className="col-10 ml-3 p-0 flex-grow-1 text-dark-500 text-left text-break">
{title} {title}
{isEnabledCompletionTracking && ( <span className="sr-only">
<span className="sr-only"> , {intl.formatMessage(complete
, {intl.formatMessage(complete ? courseOutlineMessages.completedSection
? courseOutlineMessages.completedSection : courseOutlineMessages.incompleteSection)}
: courseOutlineMessages.incompleteSection)} </span>
</span>
)}
</div> </div>
</> </>
); );

View File

@@ -28,24 +28,22 @@ const SidebarSequence = ({
} = sequence; } = sequence;
const [open, setOpen] = useState(defaultOpen); const [open, setOpen] = useState(defaultOpen);
const { activeSequenceId, units, isEnabledCompletionTracking } = useCourseOutlineSidebar(); const { activeSequenceId, units } = useCourseOutlineSidebar();
const isActiveSequence = id === activeSequenceId; const isActiveSequence = id === activeSequenceId;
const sectionTitle = ( const sectionTitle = (
<> <>
<div className="col-auto p-0" style={{ fontSize: '1.1rem' }}> <div className="col-auto p-0" style={{ fontSize: '1.1rem' }}>
<CompletionIcon completionStat={completionStat} enabled={isEnabledCompletionTracking} /> <CompletionIcon completionStat={completionStat} />
</div> </div>
<div className="col-9 d-flex flex-column flex-grow-1 ml-3 mr-auto p-0 text-left"> <div className="col-9 d-flex flex-column flex-grow-1 ml-3 mr-auto p-0 text-left">
<span className="align-middle text-dark-500">{title}</span> <span className="align-middle text-dark-500">{title}</span>
{specialExamInfo && <span className="align-middle small text-muted">{specialExamInfo}</span>} {specialExamInfo && <span className="align-middle small text-muted">{specialExamInfo}</span>}
{isEnabledCompletionTracking && ( <span className="sr-only">
<span className="sr-only"> , {intl.formatMessage(complete
, {intl.formatMessage(complete ? courseOutlineMessages.completedAssignment
? courseOutlineMessages.completedAssignment : courseOutlineMessages.incompleteAssignment)}
: courseOutlineMessages.incompleteAssignment)} </span>
</span>
)}
</div> </div>
</> </>
); );
@@ -71,7 +69,6 @@ const SidebarSequence = ({
activeUnitId={activeUnitId} activeUnitId={activeUnitId}
isFirst={index === 0} isFirst={index === 0}
isLocked={type === UNIT_ICON_TYPES.lock} isLocked={type === UNIT_ICON_TYPES.lock}
isCompletionTrackingEnabled={isEnabledCompletionTracking}
/> />
))} ))}
</ol> </ol>

View File

@@ -66,7 +66,7 @@ describe('<SidebarSequence />', () => {
expect(screen.queryByText(unit.title)).not.toBeInTheDocument(); expect(screen.queryByText(unit.title)).not.toBeInTheDocument();
}); });
it('renders correctly when sequence is not collapsed and complete and completion tracking enabled', async () => { it('renders correctly when sequence is not collapsed and complete', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
await initTestStore(); await initTestStore();
renderWithProvider({ renderWithProvider({

View File

@@ -15,7 +15,6 @@ const SidebarUnit = ({
isActive, isActive,
isLocked, isLocked,
activeUnitId, activeUnitId,
isCompletionTrackingEnabled,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const { const {
@@ -25,7 +24,6 @@ const SidebarUnit = ({
} = unit; } = unit;
const iconType = isLocked ? UNIT_ICON_TYPES.lock : icon; const iconType = isLocked ? UNIT_ICON_TYPES.lock : icon;
const completeAndEnabled = complete && isCompletionTrackingEnabled;
return ( return (
<li className={classNames({ 'bg-info-100': isActive, 'border-top border-light': !isFirst })}> <li className={classNames({ 'bg-info-100': isActive, 'border-top border-light': !isFirst })}>
@@ -38,17 +36,15 @@ const SidebarUnit = ({
}} }}
> >
<div className="col-auto p-0"> <div className="col-auto p-0">
<UnitIcon type={iconType} isCompleted={completeAndEnabled} /> <UnitIcon type={iconType} isCompleted={complete} />
</div> </div>
<div className="col-10 p-0 ml-3 text-break"> <div className="col-10 p-0 ml-3 text-break">
<span className="align-middle"> <span className="align-middle">
{title} {title}
</span> </span>
{isCompletionTrackingEnabled && ( <span className="sr-only">
<span className="sr-only"> , {intl.formatMessage(complete ? messages.completedUnit : messages.incompleteUnit)}
, {intl.formatMessage(complete ? messages.completedUnit : messages.incompleteUnit)} </span>
</span>
)}
</div> </div>
</UnitLinkWrapper> </UnitLinkWrapper>
</li> </li>
@@ -70,7 +66,6 @@ SidebarUnit.propTypes = {
courseId: PropTypes.string.isRequired, courseId: PropTypes.string.isRequired,
sequenceId: PropTypes.string.isRequired, sequenceId: PropTypes.string.isRequired,
activeUnitId: PropTypes.string.isRequired, activeUnitId: PropTypes.string.isRequired,
isCompletionTrackingEnabled: PropTypes.bool.isRequired,
}; };
export default SidebarUnit; export default SidebarUnit;

View File

@@ -50,7 +50,6 @@ describe('<SidebarUnit />', () => {
unit={{ ...unit, icon: 'video', isLocked: false }} unit={{ ...unit, icon: 'video', isLocked: false }}
isActive={false} isActive={false}
activeUnitId={unit.id} activeUnitId={unit.id}
isCompletionTrackingEnabled
{...props} {...props}
/> />
</MemoryRouter> </MemoryRouter>
@@ -69,7 +68,7 @@ describe('<SidebarUnit />', () => {
expect(container.querySelector('.text-success')).not.toBeInTheDocument(); expect(container.querySelector('.text-success')).not.toBeInTheDocument();
}); });
it('renders correctly when unit is complete and tracking enabled', async () => { it('renders correctly when unit is complete', async () => {
await initTestStore(); await initTestStore();
const container = renderWithProvider({ unit: { ...unit, complete: true } }); const container = renderWithProvider({ unit: { ...unit, complete: true } });

View File

@@ -1,10 +1,7 @@
import { import { useContext, useEffect, useState } from 'react';
useContext, useEffect, useLayoutEffect, useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { breakpoints } from '@openedx/paragon';
import { useModel } from '@src/generic/model-store'; import { useModel } from '@src/generic/model-store';
import { LOADED } from '@src/constants'; import { LOADED } from '@src/constants';
@@ -25,9 +22,7 @@ import { ID } from './constants';
export const useCourseOutlineSidebar = () => { export const useCourseOutlineSidebar = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isCollapsedOutlineSidebar = window.sessionStorage.getItem('hideCourseOutlineSidebar'); const isCollapsedOutlineSidebar = window.sessionStorage.getItem('hideCourseOutlineSidebar');
const { const { enableNavigationSidebar: isEnabledSidebar } = useSelector(getCoursewareOutlineSidebarSettings);
enableCompletionTracking: isEnabledCompletionTracking,
} = useSelector(getCoursewareOutlineSidebarSettings);
const courseOutlineShouldUpdate = useSelector(getCourseOutlineShouldUpdate); const courseOutlineShouldUpdate = useSelector(getCourseOutlineShouldUpdate);
const courseOutlineStatus = useSelector(getCourseOutlineStatus); const courseOutlineStatus = useSelector(getCourseOutlineStatus);
const sequenceStatus = useSelector(getSequenceStatus); const sequenceStatus = useSelector(getSequenceStatus);
@@ -47,7 +42,7 @@ export const useCourseOutlineSidebar = () => {
shouldDisplayFullScreen, shouldDisplayFullScreen,
} = useContext(SidebarContext); } = useContext(SidebarContext);
const isOpenSidebar = !initialSidebar && !isCollapsedOutlineSidebar; const isOpenSidebar = !initialSidebar && isEnabledSidebar && !isCollapsedOutlineSidebar;
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
const { const {
@@ -56,18 +51,13 @@ export const useCourseOutlineSidebar = () => {
} = course.entranceExamData || {}; } = course.entranceExamData || {};
const isActiveEntranceExam = entranceExamEnabled && !entranceExamPassed; const isActiveEntranceExam = entranceExamEnabled && !entranceExamPassed;
const collapseSidebar = () => {
toggleSidebar(null);
window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true');
};
const handleToggleCollapse = () => { const handleToggleCollapse = () => {
if (currentSidebar === ID) { if (currentSidebar === ID) {
collapseSidebar(); toggleSidebar(null);
window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true');
} else { } else {
toggleSidebar(ID); toggleSidebar(ID);
window.sessionStorage.removeItem('hideCourseOutlineSidebar'); window.sessionStorage.removeItem('hideCourseOutlineSidebar');
window.sessionStorage.setItem(`notificationTrayStatus.${courseId}`, 'closed');
} }
}; };
@@ -109,32 +99,17 @@ export const useCourseOutlineSidebar = () => {
}, [initialSidebar, unitId]); }, [initialSidebar, unitId]);
useEffect(() => { useEffect(() => {
if (courseOutlineStatus !== LOADED || courseOutlineShouldUpdate) { if ((isEnabledSidebar && courseOutlineStatus !== LOADED) || courseOutlineShouldUpdate) {
dispatch(getCourseOutlineStructure(courseId)); dispatch(getCourseOutlineStructure(courseId));
} }
}, [courseId, courseOutlineShouldUpdate]); }, [courseId, isEnabledSidebar, courseOutlineShouldUpdate]);
// Collapse sidebar if screen resized to a width that displays the sidebar automatically
useLayoutEffect(() => {
const handleResize = () => {
// breakpoints.large.maxWidth is 1200px and currently the breakpoint for showing the sidebar
if (currentSidebar === ID && global.innerWidth < breakpoints.large.maxWidth) {
collapseSidebar();
}
};
global.addEventListener('resize', handleResize);
return () => {
global.removeEventListener('resize', handleResize);
};
}, [isOpen]);
return { return {
courseId, courseId,
unitId, unitId,
currentSidebar, currentSidebar,
shouldDisplayFullScreen, shouldDisplayFullScreen,
isEnabledCompletionTracking, isEnabledSidebar,
isOpen, isOpen,
setIsOpen, setIsOpen,
handleToggleCollapse, handleToggleCollapse,

View File

@@ -1,5 +1,5 @@
.discussions-sidebar-frame { .discussions-sidebar-frame {
@media (--pgn-size-breakpoint-max-width-xl) { @media (max-width: -1 + map-get($grid-breakpoints, "xl")) {
max-height: calc(100vh - 65px); max-height: calc(100vh - 65px);
} }
} }

View File

@@ -104,15 +104,16 @@ export async function getCourseOutline(courseId) {
} }
/** /**
* Get waffle flag value that enables completion tracking. * Get waffle flag value that enable courseware outline sidebar and always open auxiliary sidebar.
* @param {string} courseId - The unique identifier for the course. * @param {string} courseId - The unique identifier for the course.
* @returns {Promise<{enable_completion_tracking: boolean}>} - The object * @returns {Promise<{enable_navigation_sidebar: boolean, enable_navigation_sidebar: boolean}>} - The object
* of boolean values of enabling of the completion tracking. * of boolean values of enabling of the outline sidebar and is always open auxiliary sidebar.
*/ */
export async function getCoursewareOutlineSidebarToggles(courseId) { export async function getCoursewareOutlineSidebarToggles(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/courses/${courseId}/courseware-navigation-sidebar/toggles/`); const url = new URL(`${getConfig().LMS_BASE_URL}/courses/${courseId}/courseware-navigation-sidebar/toggles/`);
const { data } = await getAuthenticatedHttpClient().get(url.href); const { data } = await getAuthenticatedHttpClient().get(url.href);
return { return {
enable_completion_tracking: data.enable_completion_tracking || false, enable_navigation_sidebar: data.enable_navigation_sidebar || false,
always_open_auxiliary_sidebar: data.always_open_auxiliary_sidebar || false,
}; };
} }

View File

@@ -111,7 +111,8 @@ describe('Data layer integration tests', () => {
axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(courseUrl).reply(200, courseMetadata);
axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks)); axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks));
axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, { axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, {
enable_completion_tracking: true, enable_navigation_sidebar: true,
always_open_auxiliary_sidebar: true,
}); });
await executeThunk(thunks.fetchCourse(courseId), store.dispatch); await executeThunk(thunks.fetchCourse(courseId), store.dispatch);
@@ -123,7 +124,8 @@ describe('Data layer integration tests', () => {
expect(state.courseware.sequenceStatus).toEqual('loading'); expect(state.courseware.sequenceStatus).toEqual('loading');
expect(state.courseware.sequenceId).toEqual(null); expect(state.courseware.sequenceId).toEqual(null);
expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({ expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({
enableCompletionTracking: true, enableNavigationSidebar: true,
alwaysOpenAuxiliarySidebar: true,
}); });
// check that at least one key camel cased, thus course data normalized // check that at least one key camel cased, thus course data normalized
@@ -137,7 +139,8 @@ describe('Data layer integration tests', () => {
axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(courseUrl).reply(200, courseMetadata);
axiosMock.onGet(learningSequencesUrlRegExp).reply(200, simpleOutline); axiosMock.onGet(learningSequencesUrlRegExp).reply(200, simpleOutline);
axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, { axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, {
enable_completion_tracking: false, enable_navigation_sidebar: false,
always_open_auxiliary_sidebar: false,
}); });
await executeThunk(thunks.fetchCourse(courseId), store.dispatch); await executeThunk(thunks.fetchCourse(courseId), store.dispatch);
@@ -149,7 +152,8 @@ describe('Data layer integration tests', () => {
expect(state.courseware.sequenceStatus).toEqual('loading'); expect(state.courseware.sequenceStatus).toEqual('loading');
expect(state.courseware.sequenceId).toEqual(null); expect(state.courseware.sequenceId).toEqual(null);
expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({ expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({
enableCompletionTracking: false, enableNavigationSidebar: false,
alwaysOpenAuxiliarySidebar: false,
}); });
// check that at least one key camel cased, thus course data normalized // check that at least one key camel cased, thus course data normalized

View File

@@ -88,11 +88,10 @@ export function fetchCourse(courseId) {
if (fetchedCoursewareOutlineSidebarTogglesResult) { if (fetchedCoursewareOutlineSidebarTogglesResult) {
const { const {
enable_completion_tracking: enableCompletionTracking, enable_navigation_sidebar: enableNavigationSidebar,
always_open_auxiliary_sidebar: alwaysOpenAuxiliarySidebar,
} = coursewareOutlineSidebarTogglesResult.value; } = coursewareOutlineSidebarTogglesResult.value;
dispatch(setCoursewareOutlineSidebarToggles( dispatch(setCoursewareOutlineSidebarToggles({ enableNavigationSidebar, alwaysOpenAuxiliarySidebar }));
{ enableCompletionTracking },
));
} }
// Log errors for each request if needed. Outline failures may occur // Log errors for each request if needed. Outline failures may occur

View File

@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DecodePageRoute should not modify the url if it does not need to be decoded 1`] = `
<div>
PageWrap: {
"children": [
" ",
[
" ",
[],
" "
],
" "
]
}
</div>
`;

View File

@@ -62,10 +62,11 @@ describe('DecodePageRoute', () => {
const props = matchPath({ const props = matchPath({
path: '/course/:courseId/home', path: '/course/:courseId/home',
}, `/course/${decodedCourseId}/home`); }, `/course/${decodedCourseId}/home`);
renderPage(props); const { container } = renderPage(props);
expect(props.pathname).toContain(decodedCourseId); expect(props.pathname).toContain(decodedCourseId);
expect(mockNavigate).not.toHaveBeenCalled(); expect(mockNavigate).not.toHaveBeenCalled();
expect(container).toMatchSnapshot();
}); });
it('should decode the url and replace the history if necessary', () => { it('should decode the url and replace the history if necessary', () => {

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { getNotices } from './api'; import { getNotices } from './api';
@@ -25,7 +25,11 @@ const NoticesProvider = ({ children }) => {
getData(); getData();
}, []); }, []);
return isRedirected === true ? null : children; return (
<div>
{isRedirected === true ? null : children}
</div>
);
}; };
NoticesProvider.propTypes = { NoticesProvider.propTypes = {

View File

@@ -8,6 +8,6 @@
} }
.upsell-bullet a { .upsell-bullet a {
color: var(--pgn-color-primary-500); color: $primary-500;
} }

View File

@@ -49,102 +49,100 @@ subscribe(APP_READY, () => {
<PathFixesProvider> <PathFixesProvider>
<NoticesProvider> <NoticesProvider>
<UserMessagesProvider> <UserMessagesProvider>
<div className="app-container"> <Routes>
<Routes> <Route path="*" element={<PageWrap><PageNotFound /></PageWrap>} />
<Route path="*" element={<PageWrap><PageNotFound /></PageWrap>} /> <Route path={ROUTES.UNSUBSCRIBE} element={<PageWrap><GoalUnsubscribe /></PageWrap>} />
<Route path={ROUTES.UNSUBSCRIBE} element={<PageWrap><GoalUnsubscribe /></PageWrap>} /> <Route path={ROUTES.REDIRECT} element={<PageWrap><CoursewareRedirectLandingPage /></PageWrap>} />
<Route path={ROUTES.REDIRECT} element={<PageWrap><CoursewareRedirectLandingPage /></PageWrap>} /> <Route
path={ROUTES.PREFERENCES_UNSUBSCRIBE}
element={
<PageWrap><PreferencesUnsubscribe /></PageWrap>
}
/>
<Route
path={DECODE_ROUTES.ACCESS_DENIED}
element={<DecodePageRoute><CourseAccessErrorPage /></DecodePageRoute>}
/>
<Route
path={DECODE_ROUTES.HOME}
element={(
<DecodePageRoute>
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome">
<OutlineTab />
</TabContainer>
</DecodePageRoute>
)}
/>
<Route
path={DECODE_ROUTES.LIVE}
element={(
<DecodePageRoute>
<TabContainer tab="lti_live" fetch={fetchLiveTab} slice="courseHome">
<LiveTab />
</TabContainer>
</DecodePageRoute>
)}
/>
<Route
path={DECODE_ROUTES.DATES}
element={(
<DecodePageRoute>
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
<DatesTab />
</TabContainer>
</DecodePageRoute>
)}
/>
<Route
path={DECODE_ROUTES.DISCUSSION}
element={(
<DecodePageRoute>
<TabContainer tab="discussion" fetch={fetchDiscussionTab} slice="courseHome">
<DiscussionTab />
</TabContainer>
</DecodePageRoute>
)}
/>
{DECODE_ROUTES.PROGRESS.map((route) => (
<Route <Route
path={ROUTES.PREFERENCES_UNSUBSCRIBE} key={route}
element={ path={route}
<PageWrap><PreferencesUnsubscribe /></PageWrap>
}
/>
<Route
path={DECODE_ROUTES.ACCESS_DENIED}
element={<DecodePageRoute><CourseAccessErrorPage /></DecodePageRoute>}
/>
<Route
path={DECODE_ROUTES.HOME}
element={( element={(
<DecodePageRoute> <DecodePageRoute>
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome"> <TabContainer
<OutlineTab /> tab="progress"
fetch={fetchProgressTab}
slice="courseHome"
isProgressTab
>
<ProgressTab />
</TabContainer> </TabContainer>
</DecodePageRoute> </DecodePageRoute>
)} )}
/> />
))}
<Route
path={DECODE_ROUTES.COURSE_END}
element={(
<DecodePageRoute>
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
<CourseExit />
</TabContainer>
</DecodePageRoute>
)}
/>
{DECODE_ROUTES.COURSEWARE.map((route) => (
<Route <Route
path={DECODE_ROUTES.LIVE} key={route}
path={route}
element={( element={(
<DecodePageRoute> <DecodePageRoute>
<TabContainer tab="lti_live" fetch={fetchLiveTab} slice="courseHome"> <CoursewareContainer />
<LiveTab />
</TabContainer>
</DecodePageRoute> </DecodePageRoute>
)} )}
/> />
<Route ))}
path={DECODE_ROUTES.DATES} </Routes>
element={(
<DecodePageRoute>
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
<DatesTab />
</TabContainer>
</DecodePageRoute>
)}
/>
<Route
path={DECODE_ROUTES.DISCUSSION}
element={(
<DecodePageRoute>
<TabContainer tab="discussion" fetch={fetchDiscussionTab} slice="courseHome">
<DiscussionTab />
</TabContainer>
</DecodePageRoute>
)}
/>
{DECODE_ROUTES.PROGRESS.map((route) => (
<Route
key={route}
path={route}
element={(
<DecodePageRoute>
<TabContainer
tab="progress"
fetch={fetchProgressTab}
slice="courseHome"
isProgressTab
>
<ProgressTab />
</TabContainer>
</DecodePageRoute>
)}
/>
))}
<Route
path={DECODE_ROUTES.COURSE_END}
element={(
<DecodePageRoute>
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
<CourseExit />
</TabContainer>
</DecodePageRoute>
)}
/>
{DECODE_ROUTES.COURSEWARE.map((route) => (
<Route
key={route}
path={route}
element={(
<DecodePageRoute>
<CoursewareContainer />
</DecodePageRoute>
)}
/>
))}
</Routes>
</div>
</UserMessagesProvider> </UserMessagesProvider>
</NoticesProvider> </NoticesProvider>
</PathFixesProvider> </PathFixesProvider>
@@ -166,15 +164,12 @@ subscribe(APP_INIT_ERROR, (error) => {
initialize({ initialize({
handlers: { handlers: {
config: () => { config: () => {
/* istanbul ignore next */
mergeConfig({ mergeConfig({
CONTACT_URL: process.env.CONTACT_URL || null, CONTACT_URL: process.env.CONTACT_URL || null,
CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null, CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null,
CREDIT_HELP_LINK_URL: process.env.CREDIT_HELP_LINK_URL || null, CREDIT_HELP_LINK_URL: process.env.CREDIT_HELP_LINK_URL || null,
DISCUSSIONS_MFE_BASE_URL: process.env.DISCUSSIONS_MFE_BASE_URL || null, DISCUSSIONS_MFE_BASE_URL: process.env.DISCUSSIONS_MFE_BASE_URL || null,
DISCOUNT_CODE_INFO_URL: process.env.DISCOUNT_CODE_INFO_URL || null,
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null, ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
ENTERPRISE_LEARNER_PORTAL_URL: process.env.ENTERPRISE_LEARNER_PORTAL_URL || null,
ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null, ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null, ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null, INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,

View File

@@ -1,24 +1,20 @@
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints; @import "~@edx/brand/paragon/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "~@edx/frontend-component-footer/dist/footer"; @import "~@edx/frontend-component-footer/dist/footer";
@import "~@edx/frontend-component-header/dist/index"; @import "~@edx/frontend-component-header/dist/index";
#root { #root {
.app-container { display: flex;
display: flex; flex-direction: column;
flex-direction: column; min-height: 100vh;
min-height: 100svh;
}
main { main {
flex-grow: 1; flex-grow: 1;
} }
#main-content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
header { header {
flex: 0 0 auto; flex: 0 0 auto;
@@ -48,7 +44,7 @@
.nav-link { .nav-link {
border-bottom: 4px solid transparent; border-bottom: 4px solid transparent;
border-top: 4px solid transparent; border-top: 4px solid transparent;
color: var(--pgn-color-gray-700); color: $gray-700;
// temporary until we can remove .btn class from dropdowns // temporary until we can remove .btn class from dropdowns
border-left: 0; border-left: 0;
@@ -58,9 +54,9 @@
&:hover, &:hover,
&:focus, &:focus,
&.active { &.active {
font-weight: var(--pgn-typography-font-weight-normal); font-weight: $font-weight-normal;
color: var(--pgn-color-primary-500); color: $primary-500;
border-bottom-color: var(--pgn-color-primary-500); border-bottom-color: $primary-500;
} }
} }
} }
@@ -79,7 +75,7 @@
} }
.sequence { .sequence {
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
border: solid 1px #eaeaea; border: solid 1px #eaeaea;
border-radius: 4px; border-radius: 4px;
} }
@@ -91,7 +87,7 @@
} }
.notification-btn { .notification-btn {
@media (--pgn-size-breakpoint-max-width-xs) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
height: 3rem; height: 3rem;
} }
} }
@@ -100,15 +96,15 @@
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
@media (--pgn-size-breakpoint-max-width-xs) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
max-width: 100%; max-width: 100%;
} }
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
margin: -1px -1px 0; margin: -1px -1px 0;
} }
@media (--pgn-size-breakpoint-max-width-xs) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
width: 100% !important; width: 100% !important;
} }
@@ -124,13 +120,13 @@
height: 3rem; height: 3rem;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: var(--pgn-color-gray-500); color: $gray-500;
white-space: nowrap; white-space: nowrap;
&:hover, &:hover,
&:focus, &:focus,
&.active { &.active {
color: var(--pgn-color-gray-700); color: $gray-700;
} }
&:focus { &:focus {
@@ -145,13 +141,13 @@
left: 0; left: 0;
right: 0; right: 0;
height: 2px; height: 2px;
background: var(--pgn-color-primary-base); background: $primary;
} }
} }
&.complete { &.complete {
background-color: #eef7e5; background-color: #eef7e5;
color: var(--pgn-color-success-base); color: $success;
} }
&:first-child { &:first-child {
@@ -215,12 +211,12 @@
min-width: 0; min-width: 0;
margin: 0 1rem; margin: 0 1rem;
text-overflow: ellipsis; text-overflow: ellipsis;
color: var(--pgn-color-gray-700); color: $gray-700;
} }
&.active { &.active {
.unit-icon { .unit-icon {
color: var(--pgn-color-primary-500); color: $primary-500;
} }
&:after { &:after {
@@ -232,7 +228,7 @@
right: auto; right: auto;
width: 2px; width: 2px;
height: auto; height: auto;
background: var(--pgn-color-primary-base); background: $primary;
} }
} }
} }
@@ -247,18 +243,18 @@
.previous-btn, .previous-btn,
.next-btn { .next-btn {
border: 1px solid var(--pgn-color-light-400) !important; border: 1px solid $light-400 !important;
color: var(--pgn-color-gray-700); color: $gray-700;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@media (--pgn-size-breakpoint-max-width-sm) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
} }
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
min-width: fit-content; min-width: fit-content;
padding-left: 2rem; padding-left: 2rem;
padding-right: 2rem; padding-right: 2rem;
@@ -269,7 +265,7 @@
border-left-width: 0; border-left-width: 0;
margin-left: 0; margin-left: 0;
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
border-left-width: 1px; border-left-width: 1px;
border-top-left-radius: 4px; border-top-left-radius: 4px;
} }
@@ -279,7 +275,7 @@
border-left-width: 1px; border-left-width: 1px;
border-right-width: 0; border-right-width: 0;
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-right-width: 1px; border-right-width: 1px;
} }
@@ -293,9 +289,9 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@media (--pgn-size-breakpoint-min-width-sm) { @media (min-width: map-get($grid-breakpoints, "sm")) {
padding-left: var(--pgn-spacing-grid-gutter-width); padding-left: $grid-gutter-width;
padding-right: var(--pgn-spacing-grid-gutter-width); padding-right: $grid-gutter-width;
} }
@media (min-width: 830px) { @media (min-width: 830px) {
@@ -313,8 +309,8 @@
// here we compensate for the padding of the parent div with "container-xl" // here we compensate for the padding of the parent div with "container-xl"
// class to ensure that the viewport width is the same as the width of the // class to ensure that the viewport width is the same as the width of the
// iframe. // iframe.
margin-left: calc(var(--pgn-spacing-grid-gutter-width) * -0.5); margin-left: -$grid-gutter-width * .5;
margin-right: calc(var(--pgn-spacing-grid-gutter-width) * -0.5); margin-right: -$grid-gutter-width * .5;
margin-bottom: 2rem; margin-bottom: 2rem;
@@ -332,13 +328,12 @@
.unit-navigation { .unit-navigation {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 5px;
max-width: 640px; max-width: 640px;
margin: 0 auto; margin: 0 auto;
@media (--pgn-size-breakpoint-max-width-xs) { @media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
flex-direction: column; flex-direction: column;
gap: var(--pgn-spacing-spacer-base); gap: $spacer;
} }
.previous-button, .previous-button,
@@ -349,12 +344,27 @@
border-radius: 6px; border-radius: 6px;
} }
} }
.next-button {
flex-basis: 75%;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
flex-basis: 100%;
}
}
.previous-button {
flex-basis: 25%;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
flex-basis: 100%;
}
}
} }
.top-unit-navigation { .top-unit-navigation {
display: flex; display: flex;
max-width: 100%; max-width: 100%;
gap: 5px;
justify-content: flex-end; justify-content: flex-end;
.next-button, .next-button,
@@ -408,8 +418,8 @@
.icon-hover { .icon-hover {
&:hover { &:hover {
color: var(--pgn-color-primary-500) !important; color: $primary-500 !important;
background-color: var(--pgn-color-light-300) !important; background-color: $light-300 !important;
} }
} }
@@ -432,7 +442,7 @@
height: 56px !important; height: 56px !important;
} }
@media (--pgn-size-breakpoint-max-width-xs) { @include media-breakpoint-down(xs) {
.course-outline-tab .pgn__card { .course-outline-tab .pgn__card {
.pgn__card-header { .pgn__card-header {
display: block; display: block;

View File

@@ -69,11 +69,15 @@ describe('app registry', () => {
const callArgs = subscribe.mock.calls[0]; const callArgs = subscribe.mock.calls[0];
expect(callArgs[0]).toEqual(APP_READY); expect(callArgs[0]).toEqual(APP_READY);
callArgs[1](); callArgs[1]();
const [rendered] = mockRender.mock.calls[0];
expect(rendered).toMatchSnapshot();
}); });
test('subscribe: APP_INIT_ERROR.', () => { test('subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element', () => {
const callArgs = subscribe.mock.calls[1]; const callArgs = subscribe.mock.calls[1];
expect(callArgs[0]).toEqual(APP_INIT_ERROR); expect(callArgs[0]).toEqual(APP_INIT_ERROR);
const error = { message: 'test-error-message' }; const error = { message: 'test-error-message' };
callArgs[1](error); callArgs[1](error);
const [rendered] = mockRender.mock.calls[0];
expect(rendered).toMatchSnapshot();
}); });
}); });

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