From 8a9dae6ddc35d2ee035a31471bd3641b6e233c66 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 16 Dec 2019 17:21:09 +0200 Subject: [PATCH 01/21] Library sort param moved from query params to url params --- src/common/routesRegexp.js | 4 ++-- src/routes/Library/Library.js | 8 ++++---- src/routes/Library/useLibrary.js | 11 ++++------- src/routes/Library/useSelectableInputs.js | 11 ++--------- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/common/routesRegexp.js b/src/common/routesRegexp.js index 113497ec5..f4128765b 100644 --- a/src/common/routesRegexp.js +++ b/src/common/routesRegexp.js @@ -12,8 +12,8 @@ const routesRegexp = { urlParamsNames: ['addonTransportUrl', 'type', 'catalogId'] }, library: { - regexp: /^\/library(?:\/([^/]*))?$/, - urlParamsNames: ['type'] + regexp: /^\/library(?:\/([^/]*)\/([^/]*))?$/, + urlParamsNames: ['type', 'sort'] }, search: { regexp: /^\/search$/, diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js index d3c081646..378612678 100644 --- a/src/routes/Library/Library.js +++ b/src/routes/Library/Library.js @@ -7,8 +7,8 @@ const useSelectableInputs = require('./useSelectableInputs'); const useItemOptions = require('./useItemOptions'); const styles = require('./styles'); -const Library = ({ urlParams, queryParams }) => { - const library = useLibrary(urlParams, queryParams); +const Library = ({ urlParams }) => { + const library = useLibrary(urlParams); const [typeSelect, sortPropSelect] = useSelectableInputs(library); const [options, optionOnSelect] = useItemOptions(); return ( @@ -73,8 +73,8 @@ const Library = ({ urlParams, queryParams }) => { Library.propTypes = { urlParams: PropTypes.exact({ type: PropTypes.string, - }), - queryParams: PropTypes.instanceOf(URLSearchParams) + sort: PropTypes.string + }) }; module.exports = Library; diff --git a/src/routes/Library/useLibrary.js b/src/routes/Library/useLibrary.js index b2426e055..69c63f56e 100644 --- a/src/routes/Library/useLibrary.js +++ b/src/routes/Library/useLibrary.js @@ -46,20 +46,17 @@ const onNewLibraryState = (library) => { } }; -const useLibrary = (urlParams, queryParams) => { +const useLibrary = (urlParams) => { const { core } = useServices(); const loadLibraryAction = React.useMemo(() => { - if (typeof urlParams.type === 'string') { + if (typeof urlParams.type === 'string' && typeof urlParams.sort === 'string') { return { action: 'Load', args: { load: 'LibraryFiltered', args: { type_name: urlParams.type, - sort_prop: queryParams.has('sort_prop') ? - queryParams.get('sort_prop') - : - null + sort_prop: urlParams.sort } } }; @@ -82,7 +79,7 @@ const useLibrary = (urlParams, queryParams) => { }; } } - }, [urlParams, queryParams]); + }, [urlParams]); return useModelState({ model: 'library', action: loadLibraryAction, diff --git a/src/routes/Library/useSelectableInputs.js b/src/routes/Library/useSelectableInputs.js index 601ce9731..645db1457 100644 --- a/src/routes/Library/useSelectableInputs.js +++ b/src/routes/Library/useSelectableInputs.js @@ -16,13 +16,7 @@ const mapSelectableInputs = (library) => { options: library.type_names .map((type) => ({ label: type, value: type })), onSelect: (event) => { - const queryParams = new URLSearchParams( - library.selected !== null ? - [['sort_prop', library.selected.sort_prop]] - : - [] - ); - window.location.replace(`#/library/${encodeURIComponent(event.value)}?${queryParams.toString()}`); + window.location.replace(`#/library/${encodeURIComponent(event.value)}/${encodeURIComponent(library.selected.sort_prop)}`); } }; const sortPropSelect = { @@ -33,9 +27,8 @@ const mapSelectableInputs = (library) => { [], options: SORT_PROP_OPTIONS, onSelect: (event) => { - const queryParams = new URLSearchParams([['sort_prop', event.value]]); if (library.selected !== null) { - window.location.replace(`#/library/${encodeURIComponent(library.selected.type_name)}?${queryParams.toString()}`); + window.location.replace(`#/library/${encodeURIComponent(library.selected.type_name)}/${encodeURIComponent(event.value)}`); } } }; From dd5e9c803bd14a3d2d81c6bc8a45774c83280eca Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 16 Dec 2019 17:41:33 +0200 Subject: [PATCH 02/21] add dev deps for tests --- package.json | 3 ++ yarn.lock | 107 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index da4427714..17da07d2d 100755 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "@storybook/addon-console": "1.2.1", "@storybook/addons": "5.2.8", "@storybook/react": "5.2.8", + "@testing-library/react": "9.4.0", + "@testing-library/react-hooks": "3.2.1", "babel-loader": "8.0.6", "clean-webpack-plugin": "3.0.0", "copy-webpack-plugin": "5.0.5", @@ -53,6 +55,7 @@ "less-loader": "5.0.0", "mini-css-extract-plugin": "0.8.0", "postcss-loader": "3.0.0", + "react-test-renderer": "16.12.0", "storybook-addon-jsx": "7.1.13", "terser-webpack-plugin": "2.2.1", "webpack": "4.41.2", diff --git a/yarn.lock b/yarn.lock index 31faaf37c..a12499a80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -905,7 +905,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6": version "7.7.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== @@ -1232,6 +1232,11 @@ react-lifecycles-compat "^3.0.4" warning "^3.0.0" +"@sheerun/mutationobserver-shim@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" + integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== + "@storybook/addon-actions@5.2.8": version "5.2.8" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.8.tgz#f63c6e1afb59e94ca1ebc776b7cad9b815e7419e" @@ -1660,6 +1665,35 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" +"@testing-library/dom@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.11.0.tgz#962a38f1a721fdb7c9e35e7579e33ff13a00eda4" + integrity sha512-Pkx9LMIGshyNbfmecjt18rrAp/ayMqGH674jYER0SXj0iG9xZc+zWRjk2Pg9JgPBDvwI//xGrI/oOQkAi4YEew== + dependencies: + "@babel/runtime" "^7.6.2" + "@sheerun/mutationobserver-shim" "^0.3.2" + "@types/testing-library__dom" "^6.0.0" + aria-query "3.0.0" + pretty-format "^24.9.0" + wait-for-expect "^3.0.0" + +"@testing-library/react-hooks@3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz#19b6caa048ef15faa69d439c469033873ea01294" + integrity sha512-1OB6Ksvlk6BCJA1xpj8/WWz0XVd1qRcgqdaFAq+xeC6l61Ucj0P6QpA5u+Db/x9gU4DCX8ziR5b66Mlfg0M2RA== + dependencies: + "@babel/runtime" "^7.5.4" + "@types/testing-library__react-hooks" "^3.0.0" + +"@testing-library/react@9.4.0": + version "9.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.4.0.tgz#b021ac8cb987c8dc54c6841875f745bf9b2e88e5" + integrity sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg== + dependencies: + "@babel/runtime" "^7.7.6" + "@testing-library/dom" "^6.11.0" + "@types/testing-library__react" "^9.1.2" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -1775,6 +1809,13 @@ "@types/history" "*" "@types/react" "*" +"@types/react-dom@*": + version "16.9.4" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.4.tgz#0b58df09a60961dcb77f62d4f1832427513420df" + integrity sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw== + dependencies: + "@types/react" "*" + "@types/react-syntax-highlighter@10.1.0": version "10.1.0" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-10.1.0.tgz#9c534e29bbe05dba9beae1234f3ae944836685d4" @@ -1782,6 +1823,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@*": + version "16.9.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4" + integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ== + dependencies: + "@types/react" "*" + "@types/react-textarea-autosize@^4.3.3": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz#6c4d2753fa1864c98c0b2b517f67bb1f6e4c46de" @@ -1812,6 +1860,29 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== +"@types/testing-library__dom@*", "@types/testing-library__dom@^6.0.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.11.0.tgz#777e3ef44cb48f2430e3fad6a2053ec39004a5d3" + integrity sha512-qUmnGl6H0wajUaO3VCJJoAeN/bQwpUzCqE/hk96NiGjIh5H4b8LfmQTOj4cHfS/9hCwO0DJytC6osHYDYiouyA== + dependencies: + pretty-format "^24.3.0" + +"@types/testing-library__react-hooks@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.1.0.tgz#04d174ce767fbcce3ccb5021d7f156e1b06008a9" + integrity sha512-QJc1sgH9DD6jbfybzugnP0sY8wPzzIq8sHDBuThzCr2ZEbyHIaAvN9ytx/tHzcWL5MqmeZJqiUm/GsythaGx3g== + dependencies: + "@types/react" "*" + "@types/react-test-renderer" "*" + +"@types/testing-library__react@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.2.tgz#e33af9124c60a010fc03a34eff8f8a34a75c4351" + integrity sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q== + dependencies: + "@types/react-dom" "*" + "@types/testing-library__dom" "*" + "@types/uglify-js@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" @@ -2211,6 +2282,14 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" + integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= + dependencies: + ast-types-flow "0.0.7" + commander "^2.11.0" + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2328,6 +2407,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types-flow@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + ast-types@0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8" @@ -3438,7 +3522,7 @@ commander@2.17.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -8601,7 +8685,7 @@ pretty-error@^2.0.2, pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.9.0: +pretty-format@^24.3.0, pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== @@ -9004,7 +9088,7 @@ react-inspector@^3.0.2: is-dom "^1.0.9" prop-types "^15.6.1" -react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== @@ -9055,6 +9139,16 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" +react-test-renderer@16.12.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f" + integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.18.0" + react-textarea-autosize@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda" @@ -10786,6 +10880,11 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" +wait-for-expect@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.1.tgz#ec204a76b0038f17711e575720aaf28505ac7185" + integrity sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw== + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From ad9db5095e5ae132988e1d63af3a5233273f1a40 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 16 Dec 2019 17:42:49 +0200 Subject: [PATCH 03/21] add tests for useSelectableSeasons hook --- tests/hooks.spec.js | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/hooks.spec.js diff --git a/tests/hooks.spec.js b/tests/hooks.spec.js new file mode 100644 index 000000000..af56f9a6e --- /dev/null +++ b/tests/hooks.spec.js @@ -0,0 +1,50 @@ +const { renderHook, act } = require('@testing-library/react-hooks'); +const useSelectableSeasons = require('../src/routes/MetaDetails/VideosList/useSelectableSeasons'); + +const videos = [{ 'season': 4 }, { 'season': 5 }, { 'season': 4 }, { 'season': 7 }]; + +describe('hooks tests', () => { + describe('useSelectableSeasons hook', () => { + it('match 4', async () => { + const { result } = renderHook(() => useSelectableSeasons(videos)); + const [, selectedSeason] = result.current; + expect(selectedSeason).toBe(4); + }); + + it('match 5', async () => { + const { result } = renderHook(() => useSelectableSeasons(videos)); + + act(() => { + const [, , , selectSeason] = result.current; + selectSeason(5); + }); + + const [, selectedSeason] = result.current; + expect(selectedSeason).toBe(5); + }); + + it('not match 6', async () => { + const { result } = renderHook(() => useSelectableSeasons(videos)); + + act(() => { + const [, , , selectSeason] = result.current; + selectSeason(6); + }); + + const [, selectedSeason] = result.current; + expect(selectedSeason).toBe(undefined); + }); + + it('not match $', async () => { + const { result } = renderHook(() => useSelectableSeasons(videos)); + + act(() => { + const [, , , selectSeason] = result.current; + selectSeason('$'); + }); + + const [, selectedSeason] = result.current; + expect(selectedSeason).toBe(undefined); + }); + }); +}); From 9de9a765207428a25e82c5d6427c26df125fa8d3 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 16 Dec 2019 18:19:49 +0200 Subject: [PATCH 04/21] installed flag moved to falgs in addon details --- src/routes/Addons/useAddonDetails.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/Addons/useAddonDetails.js b/src/routes/Addons/useAddonDetails.js index afa45eceb..016dd2ac4 100644 --- a/src/routes/Addons/useAddonDetails.js +++ b/src/routes/Addons/useAddonDetails.js @@ -11,7 +11,13 @@ const mapAddonDetailsStateWithCtx = (addonDetails, ctx) => { ...addonDetails.descriptor, content: { ...addonDetails.descriptor.content, - installed: ctx.content.addons.some((addon) => addon.transportUrl === addonDetails.descriptor.transport_url), + content: { + ...addonDetails.descriptor.content.content, + flags: { + ...addonDetails.descriptor.content.content.flags, + installed: ctx.content.addons.some((addon) => addon.transportUrl === addonDetails.descriptor.transport_url), + } + } } } : From 3354f79cafc6834608b81fc4f2729322d27002d1 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 17 Dec 2019 11:03:00 +0200 Subject: [PATCH 05/21] library regexp tests added --- tests/routesRegexp.spec.js | 47 +++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/routesRegexp.spec.js b/tests/routesRegexp.spec.js index 2d688f82b..4f6387f81 100644 --- a/tests/routesRegexp.spec.js +++ b/tests/routesRegexp.spec.js @@ -112,7 +112,52 @@ describe('routesRegexp', () => { }); }); - //TODO library route regexp + describe('library route regexp', () => { + it('match /library', async () => { + expect(Array.from('/library'.match(routesRegexp.library.regexp))) + .toEqual(['/library', undefined, undefined]); + }); + + it('match /library//', async () => { + expect(Array.from('/library//'.match(routesRegexp.library.regexp))) + .toEqual(['/library//', '', '']); + }); + + it('match /library/1/', async () => { + expect(Array.from('/library/1/'.match(routesRegexp.library.regexp))) + .toEqual(['/library/1/', '1', '']); + }); + + it('match /library//2', async () => { + expect(Array.from('/library//2'.match(routesRegexp.library.regexp))) + .toEqual(['/library//2', '', '2']); + }); + + it('match /library/1/2', async () => { + expect(Array.from('/library/1/2'.match(routesRegexp.library.regexp))) + .toEqual(['/library/1/2', '1', '2']); + }); + + it('not match /library/', async () => { + expect('/library/'.match(routesRegexp.library.regexp)) + .toBe(null); + }); + + it('not match /library///', async () => { + expect('/library///'.match(routesRegexp.library.regexp)) + .toBe(null); + }); + + it('not match /library/1', async () => { + expect('/library/1'.match(routesRegexp.library.regexp)) + .toBe(null); + }); + + it('not match /library/1/2/', async () => { + expect('/library/1/2/'.match(routesRegexp.library.regexp)) + .toBe(null); + }); + }); describe('search route regexp', () => { it('match /search', async () => { From 244551e815ea5c6c8b1b825b4f24af94ae611d6f Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Wed, 18 Dec 2019 15:22:44 +0200 Subject: [PATCH 06/21] addon details modal finalized --- .../AddonDetails/AddonDetails.js} | 72 +++++------ .../AddonDetailsModal/AddonDetails/index.js | 3 + .../AddonDetails/styles.less | 48 +++++++ .../AddonDetailsModal/AddonDetailsModal.js | 121 ++++++++++++++++++ src/common/AddonDetailsModal/index.js | 3 + src/common/AddonDetailsModal/styles.less | 34 +++++ .../AddonDetailsModal}/useAddonDetails.js | 15 +-- src/common/index.js | 2 + src/routes/Addons/AddonPrompt/index.js | 3 - src/routes/Addons/AddonPrompt/styles.less | 55 -------- src/routes/Addons/Addons.js | 30 ++++- 11 files changed, 276 insertions(+), 110 deletions(-) rename src/{routes/Addons/AddonPrompt/AddonPrompt.js => common/AddonDetailsModal/AddonDetails/AddonDetails.js} (61%) create mode 100644 src/common/AddonDetailsModal/AddonDetails/index.js create mode 100644 src/common/AddonDetailsModal/AddonDetails/styles.less create mode 100644 src/common/AddonDetailsModal/AddonDetailsModal.js create mode 100644 src/common/AddonDetailsModal/index.js create mode 100644 src/common/AddonDetailsModal/styles.less rename src/{routes/Addons => common/AddonDetailsModal}/useAddonDetails.js (71%) delete mode 100644 src/routes/Addons/AddonPrompt/index.js delete mode 100644 src/routes/Addons/AddonPrompt/styles.less diff --git a/src/routes/Addons/AddonPrompt/AddonPrompt.js b/src/common/AddonDetailsModal/AddonDetails/AddonDetails.js similarity index 61% rename from src/routes/Addons/AddonPrompt/AddonPrompt.js rename to src/common/AddonDetailsModal/AddonDetails/AddonDetails.js index 4d6f9ffc8..d2c43e9c5 100644 --- a/src/routes/Addons/AddonPrompt/AddonPrompt.js +++ b/src/common/AddonDetailsModal/AddonDetails/AddonDetails.js @@ -1,28 +1,26 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); +const Icon = require('stremio-icons/dom'); +const Image = require('stremio/common/Image'); const styles = require('./styles'); -const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, official }) => { +const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => { + const renderLogoFallback = React.useMemo(() => () => { + return ( + + ); + }, []); return ( -
-
0 })}> - { - typeof logo === 'string' && logo.length > 0 ? -
- {' -
- : - null - } - {typeof name === 'string' && name.length > 0 ? name : id} - {' '} - { - typeof version === 'string' && version.length > 0 ? - v.{version} - : - null - } +
+
+ {' + {typeof name === 'string' && name.length > 0 ? name : id}
{ typeof description === 'string' && description.length > 0 ? @@ -32,6 +30,15 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, : null } + { + typeof version === 'string' && version.length > 0 ? +
+ Version: + {version} +
+ : + null + } { typeof transportUrl === 'string' && transportUrl.length > 0 ?
@@ -57,22 +64,6 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, : null } - { - Array.isArray(catalogs) && catalogs.length > 0 ? -
- Supported catalogs: - - { - catalogs.length === 1 ? - catalogs[0].name - : - catalogs.slice(0, -1).map(({ name }) => name).join(', ') + ' & ' + catalogs[catalogs.length - 1].name - } - -
- : - null - } { !official ?
@@ -85,19 +76,16 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, ); }; -AddonPrompt.propTypes = { +AddonDetails.propTypes = { className: PropTypes.string, id: PropTypes.string, name: PropTypes.string, + version: PropTypes.string, logo: PropTypes.string, description: PropTypes.string, types: PropTypes.arrayOf(PropTypes.string), - catalogs: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string - })), - version: PropTypes.string, transportUrl: PropTypes.string, - official: PropTypes.bool + official: PropTypes.bool, }; -module.exports = AddonPrompt; +module.exports = AddonDetails; diff --git a/src/common/AddonDetailsModal/AddonDetails/index.js b/src/common/AddonDetailsModal/AddonDetails/index.js new file mode 100644 index 000000000..03cf009c7 --- /dev/null +++ b/src/common/AddonDetailsModal/AddonDetails/index.js @@ -0,0 +1,3 @@ +const AddonDetails = require('./AddonDetails'); + +module.exports = AddonDetails; diff --git a/src/common/AddonDetailsModal/AddonDetails/styles.less b/src/common/AddonDetailsModal/AddonDetails/styles.less new file mode 100644 index 000000000..2d69af547 --- /dev/null +++ b/src/common/AddonDetailsModal/AddonDetails/styles.less @@ -0,0 +1,48 @@ +.addon-details-container { + .title-container { + .logo, .icon { + float: left; + width: 5rem; + height: 5rem; + margin-right: 1rem; + padding: 0.5rem; + background-color: var(--color-backgrounddarker); + } + + .logo { + object-fit: contain; + object-position: center; + } + + .icon { + fill: var(--color-surfacelighter); + } + + .name { + font-size: 2.4rem; + font-weight: 300; + line-height: 5rem; + } + } + + .section-container { + margin-top: 1rem; + + .section-header { + font-size: 1.2rem; + } + + .section-label { + font-size: 1.2rem; + font-weight: 300; + + &.transport-url-label { + user-select: text; + } + + &.disclaimer-label { + font-style: italic; + } + } + } +} \ No newline at end of file diff --git a/src/common/AddonDetailsModal/AddonDetailsModal.js b/src/common/AddonDetailsModal/AddonDetailsModal.js new file mode 100644 index 000000000..fd20da40f --- /dev/null +++ b/src/common/AddonDetailsModal/AddonDetailsModal.js @@ -0,0 +1,121 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const ModalDialog = require('stremio/common/ModalDialog'); +const { useServices } = require('stremio/services'); +const AddonDetails = require('./AddonDetails'); +const useAddonDetails = require('./useAddonDetails'); +const styles = require('./styles'); + +const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { + const { core } = useServices(); + const addonDetails = useAddonDetails(transportUrl); + const modalButtons = React.useMemo(() => { + if (addonDetails.descriptor === null || addonDetails.descriptor.content.type !== 'Ready') { + return null; + } + + const cancelOnClick = (event) => { + if (typeof onCloseRequest === 'function') { + onCloseRequest({ + type: 'cancel', + reactEvent: event, + nativeEvent: event.nativeEvent + }); + } + }; + const installOnClick = (event) => { + core.dispatch({ + action: 'AddonOp', + args: { + addonOp: 'Install', + args: addonDetails.descriptor.content.content + } + }); + if (typeof onCloseRequest === 'function') { + onCloseRequest({ + type: 'install', + reactEvent: event, + nativeEvent: event.nativeEvent + }); + } + }; + const uninstallOnClick = (event) => { + core.dispatch({ + action: 'AddonOp', + args: { + addonOp: 'Uninstall', + args: { + transport_url: addonDetails.descriptor.content.content.transportUrl + } + } + }); + if (typeof onCloseRequest === 'function') { + onCloseRequest({ + type: 'uninstall', + reactEvent: event, + nativeEvent: event.nativeEvent + }); + } + }; + return [ + { + className: styles['cancel-button'], + label: 'Cancel', + props: { + onClick: cancelOnClick + } + }, + addonDetails.descriptor.content.content.installed ? + { + className: styles['uninstall-button'], + label: 'Uninstall', + props: { + onClick: uninstallOnClick + } + } + : + { + + className: styles['install-button'], + label: 'Install', + props: { + onClick: installOnClick + } + } + ]; + }, [addonDetails, onCloseRequest]); + return ( + + { + addonDetails.descriptor === null || addonDetails.descriptor.content.type === 'Loading' ? +
+ Loading addon manifest from {transportUrl} +
+ : + addonDetails.descriptor.content.type === 'Err' ? +
+ Failed to get addon manifest from {transportUrl} +
+ : + + } +
+ ); +}; + +AddonDetailsModal.propTypes = { + transportUrl: PropTypes.string, + onCloseRequest: PropTypes.func +}; + +module.exports = AddonDetailsModal; diff --git a/src/common/AddonDetailsModal/index.js b/src/common/AddonDetailsModal/index.js new file mode 100644 index 000000000..b2d5f6838 --- /dev/null +++ b/src/common/AddonDetailsModal/index.js @@ -0,0 +1,3 @@ +const AddonDetailsModal = require('./AddonDetailsModal'); + +module.exports = AddonDetailsModal; diff --git a/src/common/AddonDetailsModal/styles.less b/src/common/AddonDetailsModal/styles.less new file mode 100644 index 000000000..146dc9a2f --- /dev/null +++ b/src/common/AddonDetailsModal/styles.less @@ -0,0 +1,34 @@ +:import('~stremio/common/ModalDialog/styles.less') { + label: label; +} + +.addon-details-modal-container { + .addon-details-container, .addon-details-message-container { + width: 60rem; + max-width: 100%; + } + + .install-button, .uninstall-button, .cancel-button { + .label { + font-size: 1.2rem; + font-weight: 500; + } + } + + .cancel-button, .uninstall-button { + background-color: transparent; + outline-width: var(--focus-outline-size); + outline-offset: calc(-1 * var(--focus-outline-size)); + outline-color: var(--color-surfacedarker); + outline-style: solid; + + &:hover, &:focus { + filter: none; + background: var(--color-surfacelight); + } + + .label { + color: var(--color-surfacedarker); + } + } +} \ No newline at end of file diff --git a/src/routes/Addons/useAddonDetails.js b/src/common/AddonDetailsModal/useAddonDetails.js similarity index 71% rename from src/routes/Addons/useAddonDetails.js rename to src/common/AddonDetailsModal/useAddonDetails.js index 016dd2ac4..71b9c2a9d 100644 --- a/src/routes/Addons/useAddonDetails.js +++ b/src/common/AddonDetailsModal/useAddonDetails.js @@ -1,5 +1,5 @@ const React = require('react'); -const { useModelState } = require('stremio/common'); +const useModelState = require('stremio/common/useModelState'); const initAddonDetailsState = () => ({ descriptor: null @@ -13,10 +13,7 @@ const mapAddonDetailsStateWithCtx = (addonDetails, ctx) => { ...addonDetails.descriptor.content, content: { ...addonDetails.descriptor.content.content, - flags: { - ...addonDetails.descriptor.content.content.flags, - installed: ctx.content.addons.some((addon) => addon.transportUrl === addonDetails.descriptor.transport_url), - } + installed: ctx.content.addons.some((addon) => addon.transportUrl === addonDetails.descriptor.transportUrl), } } } @@ -25,15 +22,15 @@ const mapAddonDetailsStateWithCtx = (addonDetails, ctx) => { return { descriptor }; }; -const useAddonDetails = (queryParams) => { +const useAddonDetails = (transportUrl) => { const loadAddonDetailsAction = React.useMemo(() => { - if (queryParams.has('addon')) { + if (typeof transportUrl === 'string') { return { action: 'Load', args: { load: 'AddonDetails', args: { - transport_url: queryParams.get('addon') + transport_url: transportUrl } } }; @@ -42,7 +39,7 @@ const useAddonDetails = (queryParams) => { action: 'Unload' }; } - }, [queryParams]); + }, [transportUrl]); return useModelState({ model: 'addon_details', action: loadAddonDetailsAction, diff --git a/src/common/index.js b/src/common/index.js index e1d2437a8..7fedd9f36 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,3 +1,4 @@ +const AddonDetailsModal = require('./AddonDetailsModal'); const Button = require('./Button'); const Checkbox = require('./Checkbox'); const ColorInput = require('./ColorInput'); @@ -29,6 +30,7 @@ const useSpreadState = require('./useSpreadState'); const useUser = require('./useUser'); module.exports = { + AddonDetailsModal, Button, Checkbox, ColorInput, diff --git a/src/routes/Addons/AddonPrompt/index.js b/src/routes/Addons/AddonPrompt/index.js deleted file mode 100644 index 43689ec70..000000000 --- a/src/routes/Addons/AddonPrompt/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const AddonPrompt = require('./AddonPrompt'); - -module.exports = AddonPrompt; diff --git a/src/routes/Addons/AddonPrompt/styles.less b/src/routes/Addons/AddonPrompt/styles.less deleted file mode 100644 index b58aafceb..000000000 --- a/src/routes/Addons/AddonPrompt/styles.less +++ /dev/null @@ -1,55 +0,0 @@ -.addon-prompt-container { - .title-container { - font-size: 3rem; - font-weight: 300; - word-break: break-all; - - &.title-with-logo-container { - &::first-line { - line-height: 5rem; - } - } - - .logo-container { - width: 5rem; - height: 5rem; - margin-right: 0.5rem; - background-color: var(--color-surfacelight20); - float: left; - - .logo { - display: block; - width: 100%; - height: 100%; - object-fit: contain; - object-position: center; - } - } - - .version-container { - font-size: 1.5rem; - font-weight: 400; - } - } - - .section-container { - margin-top: 1rem; - - .section-header { - font-size: 1.2rem; - } - - .section-label { - font-size: 1.2rem; - font-weight: 300; - - &.transport-url-label { - user-select: text; - } - - &.disclaimer-label { - font-style: italic; - } - } - } -} \ No newline at end of file diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 3f7874666..66c995b67 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -1,7 +1,7 @@ const React = require('react'); const PropTypes = require('prop-types'); const Icon = require('stremio-icons/dom'); -const { Button, Multiselect, NavBar, TextInput, SharePrompt, ModalDialog, useBinaryState } = require('stremio/common'); +const { AddonDetailsModal, Button, Multiselect, NavBar, TextInput, SharePrompt, ModalDialog, useBinaryState } = require('stremio/common'); const Addon = require('./Addon'); const useAddons = require('./useAddons'); const useSelectableInputs = require('./useSelectableInputs'); @@ -19,8 +19,20 @@ const navigateToAddonDetails = (addonsCatalogRequest, transportUrl) => { } }; +const clearAddonDetails = (addonsCatalogRequest) => { + if (addonsCatalogRequest !== null) { + const addonTransportUrl = encodeURIComponent(addonsCatalogRequest.base); + const catalogId = encodeURIComponent(addonsCatalogRequest.path.id); + const type = encodeURIComponent(addonsCatalogRequest.path.type_name); + window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}`); + } else { + window.location.replace('#/addons'); + } +}; + const Addons = ({ urlParams, queryParams }) => { const addons = useAddons(urlParams); + const detailsTransportUrl = queryParams.get('addon'); const selectInputs = useSelectableInputs(addons); const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false); const addAddonUrlInputRef = React.useRef(null); @@ -68,6 +80,13 @@ const Addons = ({ urlParams, queryParams }) => { null; navigateToAddonDetails(addonsCatalogRequest, event.dataset.transportUrl); }, [addons]); + const closeAddonDetails = React.useCallback(() => { + const addonsCatalogRequest = addons.catalog_resource !== null ? + addons.catalog_resource.request + : + null; + clearAddonDetails(addonsCatalogRequest); + }, [addons]); React.useLayoutEffect(() => { closeAddAddonModal(); setSearch(''); @@ -183,6 +202,15 @@ const Addons = ({ urlParams, queryParams }) => { : null } + { + typeof detailsTransportUrl === 'string' ? + + : + null + }
); }; From e1905f928d9f16857aca49378e4e896beb992660 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Wed, 18 Dec 2019 16:47:07 +0200 Subject: [PATCH 07/21] NavTabButton selected by bool prop instead of match against location hash --- src/common/MainNavBar/MainNavBar.js | 18 ++++++++++++------ src/common/NavBar/NavBar.js | 6 ++++-- .../NavBar/NavTabButton/NavTabButton.js | 19 +++---------------- src/common/NavBar/NavTabButton/styles.less | 2 +- src/routes/Board/Board.js | 2 +- src/routes/Discover/Discover.js | 2 +- src/routes/Library/Library.js | 2 +- 7 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/common/MainNavBar/MainNavBar.js b/src/common/MainNavBar/MainNavBar.js index 507f8d229..55bdce638 100644 --- a/src/common/MainNavBar/MainNavBar.js +++ b/src/common/MainNavBar/MainNavBar.js @@ -3,17 +3,22 @@ const PropTypes = require('prop-types'); const NavBar = require('stremio/common/NavBar'); const TABS = [ - { label: 'Board', icon: 'ic_board', href: '#/' }, - { label: 'Discover', icon: 'ic_discover', href: '#/discover' }, - { label: 'Library', icon: 'ic_library', href: '#/library' } + { id: 'board', label: 'Board', icon: 'ic_board', href: '#/' }, + { id: 'discover', label: 'Discover', icon: 'ic_discover', href: '#/discover' }, + { id: 'library', label: 'Library', icon: 'ic_library', href: '#/library' } ]; -const MainNavBar = React.memo(({ className }) => { +const MainNavBar = React.memo(({ className, selected }) => { return ( ({ + label, + icon, + href, + selected: id === selected + }))} searchBar={true} addonsButton={true} fullscreenButton={true} @@ -26,7 +31,8 @@ const MainNavBar = React.memo(({ className }) => { MainNavBar.displayName = 'MainNavBar'; MainNavBar.propTypes = { - className: PropTypes.string + className: PropTypes.string, + selected: PropTypes.string }; module.exports = MainNavBar; diff --git a/src/common/NavBar/NavBar.js b/src/common/NavBar/NavBar.js index 92663af21..6fe9859ed 100644 --- a/src/common/NavBar/NavBar.js +++ b/src/common/NavBar/NavBar.js @@ -28,13 +28,14 @@ const NavBar = React.memo(({ className, backButton, tabs, title, searchBar, addo } { Array.isArray(tabs) && tabs.length > 0 ? - tabs.slice(0, 4).map(({ href = '', icon = '', label = '', onClick }) => ( + tabs.slice(0, 4).map(({ href, icon, label, selected, onClick }, index) => ( )) @@ -88,6 +89,7 @@ NavBar.propTypes = { icon: PropTypes.string, label: PropTypes.string, href: PropTypes.string, + selected: PropTypes.bool, onClick: PropTypes.func })), title: PropTypes.string, diff --git a/src/common/NavBar/NavTabButton/NavTabButton.js b/src/common/NavBar/NavTabButton/NavTabButton.js index 3af4bd7fb..7a525b868 100644 --- a/src/common/NavBar/NavTabButton/NavTabButton.js +++ b/src/common/NavBar/NavTabButton/NavTabButton.js @@ -3,25 +3,11 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); -const routesRegexp = require('stremio/common/routesRegexp'); -const useRouteActive = require('stremio/common/useRouteActive'); const styles = require('./styles'); -const NavTabButton = ({ className, icon, label, href, onClick }) => { - const routeRegexp = React.useMemo(() => { - if (typeof href === 'string' && href.startsWith('#')) { - for (const { regexp } of Object.values(routesRegexp)) { - if (href.slice(1).match(regexp)) { - return regexp; - } - } - } - - return null; - }, [href]); - const routeActive = useRouteActive(routeRegexp); +const NavTabButton = ({ className, icon, label, href, selected, onClick }) => { return ( -