diff --git a/.changeset/angry-dolphins-fry.md b/.changeset/angry-dolphins-fry.md new file mode 100644 index 000000000..b7596dfa0 --- /dev/null +++ b/.changeset/angry-dolphins-fry.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix: exports diff --git a/.changeset/angry-hairs-enjoy.md b/.changeset/angry-hairs-enjoy.md new file mode 100644 index 000000000..78c9d7ca4 --- /dev/null +++ b/.changeset/angry-hairs-enjoy.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +refactor: remove AppBridgeCreateAsset, please use `@frontify/app-bridge-app` diff --git a/.changeset/brave-queens-smile.md b/.changeset/brave-queens-smile.md new file mode 100644 index 000000000..5aa11b4ab --- /dev/null +++ b/.changeset/brave-queens-smile.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: remove appBridgePlatformApp diff --git a/.changeset/brave-queens-work.md b/.changeset/brave-queens-work.md new file mode 100644 index 000000000..c8892e305 --- /dev/null +++ b/.changeset/brave-queens-work.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main to dev diff --git a/.changeset/calm-candles-wink.md b/.changeset/calm-candles-wink.md new file mode 100644 index 000000000..b22258754 --- /dev/null +++ b/.changeset/calm-candles-wink.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat(AppBridgePlatformApp): only one app bridge per iframe diff --git a/.changeset/clever-suns-laugh.md b/.changeset/clever-suns-laugh.md new file mode 100644 index 000000000..3cbac03e7 --- /dev/null +++ b/.changeset/clever-suns-laugh.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: rename brand sdk name diff --git a/.changeset/cuddly-pets-hammer.md b/.changeset/cuddly-pets-hammer.md new file mode 100644 index 000000000..b2bab11a7 --- /dev/null +++ b/.changeset/cuddly-pets-hammer.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +fix: document page duplication return type diff --git a/.changeset/curly-pots-scream.md b/.changeset/curly-pots-scream.md new file mode 100644 index 000000000..7e893ff38 --- /dev/null +++ b/.changeset/curly-pots-scream.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix: extend AppBridgeThemeEvent to include assetsChosen diff --git a/.changeset/cyan-cougars-invite.md b/.changeset/cyan-cougars-invite.md new file mode 100644 index 000000000..843fe4fb0 --- /dev/null +++ b/.changeset/cyan-cougars-invite.md @@ -0,0 +1,5 @@ +--- +"@frontify/guideline-themes": patch +--- + +feat: starting guideline-themes package diff --git a/.changeset/dry-mangos-fix.md b/.changeset/dry-mangos-fix.md new file mode 100644 index 000000000..196d8f2e3 --- /dev/null +++ b/.changeset/dry-mangos-fix.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: add a navigate command diff --git a/.changeset/dull-planes-worry.md b/.changeset/dull-planes-worry.md new file mode 100644 index 000000000..c3f65bd72 --- /dev/null +++ b/.changeset/dull-planes-worry.md @@ -0,0 +1,5 @@ +--- +"@frontify/guideline-themes": patch +--- + +chore: update the @frontify/sidebar-settings dependency diff --git a/.changeset/early-masks-appear.md b/.changeset/early-masks-appear.md new file mode 100644 index 000000000..59bc79263 --- /dev/null +++ b/.changeset/early-masks-appear.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat(AppBridgeTheme): add context hooks diff --git a/.changeset/eighty-ants-happen.md b/.changeset/eighty-ants-happen.md new file mode 100644 index 000000000..a0d204c05 --- /dev/null +++ b/.changeset/eighty-ants-happen.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +refactor(themes): add color utilities diff --git a/.changeset/eleven-shrimps-jog.md b/.changeset/eleven-shrimps-jog.md new file mode 100644 index 000000000..de878d704 --- /dev/null +++ b/.changeset/eleven-shrimps-jog.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +chore(AppBridgeTheme): add vitest to app bridge theme diff --git a/.changeset/empty-jobs-add.md b/.changeset/empty-jobs-add.md new file mode 100644 index 000000000..81e06a474 --- /dev/null +++ b/.changeset/empty-jobs-add.md @@ -0,0 +1,5 @@ +--- +"@frontify/guideline-themes": patch +--- + +refactor(guideline-themes): removes themeConfigExport.settings diff --git a/.changeset/fast-houses-hunt.md b/.changeset/fast-houses-hunt.md new file mode 100644 index 000000000..1c92b00cc --- /dev/null +++ b/.changeset/fast-houses-hunt.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: AppBridgeTheme update context keys diff --git a/.changeset/few-islands-build.md b/.changeset/few-islands-build.md new file mode 100644 index 000000000..542e6a686 --- /dev/null +++ b/.changeset/few-islands-build.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +refactor(AppBridgeTheme): version number change diff --git a/.changeset/five-mangos-search.md b/.changeset/five-mangos-search.md new file mode 100644 index 000000000..72a2a5743 --- /dev/null +++ b/.changeset/five-mangos-search.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: merge `main` into `dev` diff --git a/.changeset/four-crabs-bake.md b/.changeset/four-crabs-bake.md new file mode 100644 index 000000000..8e28ee068 --- /dev/null +++ b/.changeset/four-crabs-bake.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: (AppBridgeTheme) update appBridgeThemeStub with context and state diff --git a/.changeset/four-ravens-try.md b/.changeset/four-ravens-try.md new file mode 100644 index 000000000..e64c4f358 --- /dev/null +++ b/.changeset/four-ravens-try.md @@ -0,0 +1,5 @@ +--- +"@frontify/guideline-themes": patch +--- + +fix(guideline-themes): export types ungroupped diff --git a/.changeset/fuzzy-walls-repeat.md b/.changeset/fuzzy-walls-repeat.md new file mode 100644 index 000000000..3d0f4d81a --- /dev/null +++ b/.changeset/fuzzy-walls-repeat.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: Merge main into dev diff --git a/.changeset/gentle-months-bake.md b/.changeset/gentle-months-bake.md new file mode 100644 index 000000000..17e90d922 --- /dev/null +++ b/.changeset/gentle-months-bake.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: add `isPlatformAppsDialogOpen` to context diff --git a/.changeset/giant-shrimps-dream.md b/.changeset/giant-shrimps-dream.md new file mode 100644 index 000000000..f26a59dcf --- /dev/null +++ b/.changeset/giant-shrimps-dream.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": minor +--- + +feat: add open/close search dialog commands to AppBridgeTheme diff --git a/.changeset/good-forks-repair.md b/.changeset/good-forks-repair.md new file mode 100644 index 000000000..c8892e305 --- /dev/null +++ b/.changeset/good-forks-repair.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main to dev diff --git a/.changeset/gorgeous-seas-search.md b/.changeset/gorgeous-seas-search.md new file mode 100644 index 000000000..44f330623 --- /dev/null +++ b/.changeset/gorgeous-seas-search.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge branch `main` into dev diff --git a/.changeset/great-dolls-enjoy.md b/.changeset/great-dolls-enjoy.md new file mode 100644 index 000000000..74c47b9b8 --- /dev/null +++ b/.changeset/great-dolls-enjoy.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: templateContext and NavigationItem types diff --git a/.changeset/hip-days-applaud.md b/.changeset/hip-days-applaud.md new file mode 100644 index 000000000..72a2a5743 --- /dev/null +++ b/.changeset/hip-days-applaud.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: merge `main` into `dev` diff --git a/.changeset/hip-foxes-listen.md b/.changeset/hip-foxes-listen.md new file mode 100644 index 000000000..72d239439 --- /dev/null +++ b/.changeset/hip-foxes-listen.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: add documentId to GuidelineDocumentCategory events diff --git a/.changeset/khaki-dryers-shout.md b/.changeset/khaki-dryers-shout.md new file mode 100644 index 000000000..d6ace4e4f --- /dev/null +++ b/.changeset/khaki-dryers-shout.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": major +--- + +feat: modify `DocumentSection` and `DocumentSectionApi` type to allow `title` to be `null`. diff --git a/.changeset/kind-fishes-flash.md b/.changeset/kind-fishes-flash.md new file mode 100644 index 000000000..185052be7 --- /dev/null +++ b/.changeset/kind-fishes-flash.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: add context.template to AppBridgeTheme diff --git a/.changeset/light-crews-report.md b/.changeset/light-crews-report.md new file mode 100644 index 000000000..1003ebd33 --- /dev/null +++ b/.changeset/light-crews-report.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge main into dev diff --git a/.changeset/little-eagles-joke.md b/.changeset/little-eagles-joke.md new file mode 100644 index 000000000..c0276207c --- /dev/null +++ b/.changeset/little-eagles-joke.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +refactor: deprecating navigateToDocumentSection in favor of navigateToSectionHeading. diff --git a/.changeset/lovely-books-hear.md b/.changeset/lovely-books-hear.md new file mode 100644 index 000000000..d272a7449 --- /dev/null +++ b/.changeset/lovely-books-hear.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: switch to platformAppBridge to v4 diff --git a/.changeset/lucky-taxis-kneel.md b/.changeset/lucky-taxis-kneel.md new file mode 100644 index 000000000..79b38c09d --- /dev/null +++ b/.changeset/lucky-taxis-kneel.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: add user state variable diff --git a/.changeset/mighty-brooms-lay.md b/.changeset/mighty-brooms-lay.md new file mode 100644 index 000000000..782101f06 --- /dev/null +++ b/.changeset/mighty-brooms-lay.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main into dev diff --git a/.changeset/modern-experts-dream.md b/.changeset/modern-experts-dream.md new file mode 100644 index 000000000..58a7b60aa --- /dev/null +++ b/.changeset/modern-experts-dream.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix(AppBridgeThemes): fix build and codeowners diff --git a/.changeset/nasty-feet-repair.md b/.changeset/nasty-feet-repair.md new file mode 100644 index 000000000..da4af8aed --- /dev/null +++ b/.changeset/nasty-feet-repair.md @@ -0,0 +1,6 @@ +--- +"@frontify/app-bridge": patch +--- + +fix(useDocumentNavigation): Fixed an issue that prevented debounced callbacks not to be executed +fix(usePortalNavigation): Fixed an issue that prevented debounced callbacks not to be executed diff --git a/.changeset/neat-plants-smile.md b/.changeset/neat-plants-smile.md new file mode 100644 index 000000000..1003ebd33 --- /dev/null +++ b/.changeset/neat-plants-smile.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge main into dev diff --git a/.changeset/nervous-walls-repair.md b/.changeset/nervous-walls-repair.md new file mode 100644 index 000000000..88164ccee --- /dev/null +++ b/.changeset/nervous-walls-repair.md @@ -0,0 +1,6 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: Adjust PlatformAppContext +refactor: rename from type to surface diff --git a/.changeset/new-dots-clean.md b/.changeset/new-dots-clean.md new file mode 100644 index 000000000..09de6f3c2 --- /dev/null +++ b/.changeset/new-dots-clean.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": minor +--- + +feat: add command to open and close the platform apps dialog diff --git a/.changeset/new-zebras-unite.md b/.changeset/new-zebras-unite.md new file mode 100644 index 000000000..ff932b225 --- /dev/null +++ b/.changeset/new-zebras-unite.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix: AppBridgeTheme command typing diff --git a/.changeset/nine-roses-sleep.md b/.changeset/nine-roses-sleep.md new file mode 100644 index 000000000..ff1af532e --- /dev/null +++ b/.changeset/nine-roses-sleep.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Fix: readd method diff --git a/.changeset/ninety-lions-love.md b/.changeset/ninety-lions-love.md new file mode 100644 index 000000000..e7f4b965e --- /dev/null +++ b/.changeset/ninety-lions-love.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +fix: AssetChooserResult Type as alternative text is prefixed with computed diff --git a/.changeset/odd-geckos-end.md b/.changeset/odd-geckos-end.md new file mode 100644 index 000000000..5a7a253de --- /dev/null +++ b/.changeset/odd-geckos-end.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +refactor: rename brandsdk name from secretRequest to secureRequest diff --git a/.changeset/old-cougars-study.md b/.changeset/old-cougars-study.md new file mode 100644 index 000000000..5acf1ad7b --- /dev/null +++ b/.changeset/old-cougars-study.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": minor +--- + +feat(Asset): added the property alternativeText diff --git a/.changeset/orange-jokes-listen.md b/.changeset/orange-jokes-listen.md new file mode 100644 index 000000000..1003ebd33 --- /dev/null +++ b/.changeset/orange-jokes-listen.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge main into dev diff --git a/.changeset/pink-bugs-behave.md b/.changeset/pink-bugs-behave.md new file mode 100644 index 000000000..055f62a9d --- /dev/null +++ b/.changeset/pink-bugs-behave.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: adds `setAssetIdsByBlockAssetKey` to AppBridgeBlock diff --git a/.changeset/pink-geckos-change.md b/.changeset/pink-geckos-change.md new file mode 100644 index 000000000..14e14bdc2 --- /dev/null +++ b/.changeset/pink-geckos-change.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat(usePageTemplateSettings): returns also templateThemeSettings diff --git a/.changeset/pink-lobsters-beg.md b/.changeset/pink-lobsters-beg.md new file mode 100644 index 000000000..835c0e161 --- /dev/null +++ b/.changeset/pink-lobsters-beg.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat(AppBridgeThemes): initial alpha release of AppBridgeThemes diff --git a/.changeset/polite-eels-shake.md b/.changeset/polite-eels-shake.md new file mode 100644 index 000000000..ee9ca6583 --- /dev/null +++ b/.changeset/polite-eels-shake.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: adjust the color type to have a revision diff --git a/.changeset/poor-files-press.md b/.changeset/poor-files-press.md new file mode 100644 index 000000000..4bf58e24f --- /dev/null +++ b/.changeset/poor-files-press.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +refactor(appBridgeThemes): remove utilities folder diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..419af1d3e --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,102 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "@frontify/app-bridge": "3.0.0", + "@frontify/frontify-cli": "5.5.3", + "@frontify/guideline-blocks-settings": "0.29.16", + "@frontify/sidebar-settings": "0.8.1", + "@frontify/platform-app": "0.0.5", + "@frontify/app-bridge-theme": "1.0.0-alpha.1", + "@frontify/app-bridge-app": "0.0.5", + "@frontify/guideline-themes": "0.0.0" + }, + "changesets": [ + "angry-dolphins-fry", + "angry-hairs-enjoy", + "brave-queens-smile", + "brave-queens-work", + "calm-candles-wink", + "clever-suns-laugh", + "cuddly-pets-hammer", + "curly-pots-scream", + "cyan-cougars-invite", + "dry-mangos-fix", + "dull-planes-worry", + "early-masks-appear", + "eighty-ants-happen", + "eleven-shrimps-jog", + "empty-jobs-add", + "fast-houses-hunt", + "few-islands-build", + "five-mangos-search", + "four-crabs-bake", + "four-ravens-try", + "fuzzy-walls-repeat", + "gentle-months-bake", + "giant-shrimps-dream", + "good-forks-repair", + "gorgeous-seas-search", + "great-dolls-enjoy", + "hip-days-applaud", + "hip-foxes-listen", + "khaki-dryers-shout", + "kind-fishes-flash", + "light-crews-report", + "little-eagles-joke", + "lovely-books-hear", + "lucky-taxis-kneel", + "mighty-brooms-lay", + "modern-experts-dream", + "nasty-feet-repair", + "neat-plants-smile", + "nervous-walls-repair", + "new-dots-clean", + "new-zebras-unite", + "nine-roses-sleep", + "ninety-lions-love", + "odd-geckos-end", + "old-cougars-study", + "orange-jokes-listen", + "pink-bugs-behave", + "pink-geckos-change", + "pink-lobsters-beg", + "polite-eels-shake", + "poor-files-press", + "pretty-items-marry", + "purple-seahorses-watch", + "real-jeans-guess", + "red-islands-report", + "rich-olives-repeat", + "shaggy-mugs-speak", + "shiny-beds-thank", + "shiny-poets-enjoy", + "shy-jobs-protect", + "six-snails-carry", + "slimy-cats-repeat", + "smart-suns-type", + "smooth-planets-pay", + "soft-mugs-peel", + "sour-cheetahs-buy", + "spicy-books-shout", + "stale-hairs-wash", + "strange-radios-bow", + "strong-tools-check", + "tasty-coins-jam", + "tasty-vans-cough", + "ten-guests-wash", + "thirty-eggs-judge", + "thirty-pears-lay", + "three-avocados-push", + "tough-nails-shout", + "tricky-boats-sip", + "twelve-keys-begin", + "violet-elephants-tan", + "warm-hounds-refuse", + "wild-bags-judge", + "wise-zoos-jam", + "witty-rivers-smile", + "witty-tools-check", + "young-crews-hope" + ] +} diff --git a/.changeset/pretty-items-marry.md b/.changeset/pretty-items-marry.md new file mode 100644 index 000000000..d7a034a0f --- /dev/null +++ b/.changeset/pretty-items-marry.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +fix: useGroupedDocuments and useUngroupedDocuments flaky tests diff --git a/.changeset/purple-seahorses-watch.md b/.changeset/purple-seahorses-watch.md new file mode 100644 index 000000000..0baea1220 --- /dev/null +++ b/.changeset/purple-seahorses-watch.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: add activeSectionHeadingId to context diff --git a/.changeset/real-jeans-guess.md b/.changeset/real-jeans-guess.md new file mode 100644 index 000000000..c8892e305 --- /dev/null +++ b/.changeset/real-jeans-guess.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main to dev diff --git a/.changeset/red-islands-report.md b/.changeset/red-islands-report.md new file mode 100644 index 000000000..8460e449a --- /dev/null +++ b/.changeset/red-islands-report.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +refactor: Method to reflect feature diff --git a/.changeset/rich-olives-repeat.md b/.changeset/rich-olives-repeat.md new file mode 100644 index 000000000..44f330623 --- /dev/null +++ b/.changeset/rich-olives-repeat.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge branch `main` into dev diff --git a/.changeset/shaggy-mugs-speak.md b/.changeset/shaggy-mugs-speak.md new file mode 100644 index 000000000..0bef3f30f --- /dev/null +++ b/.changeset/shaggy-mugs-speak.md @@ -0,0 +1,7 @@ +--- +"@frontify/app-bridge": patch +--- + +- feat(useDocumentSection): Subscribe hook to emitter event listeners. A new emitter type, `AppBridge:GuidelineDocumentSection:Action` has been added. This emitter can be used to add and remove items from the sections saved in the hook state. + +- feat(useDocumentSection): `navigationItems` is now returned from this hook. This array filters out sections with an unreadable title and should be used to create section navigation links. diff --git a/.changeset/shiny-beds-thank.md b/.changeset/shiny-beds-thank.md new file mode 100644 index 000000000..ba4ade4f3 --- /dev/null +++ b/.changeset/shiny-beds-thank.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix(AppBridgeTheme): update imports diff --git a/.changeset/shiny-poets-enjoy.md b/.changeset/shiny-poets-enjoy.md new file mode 100644 index 000000000..6a8322b3b --- /dev/null +++ b/.changeset/shiny-poets-enjoy.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat(AppBridgeTheme): add use language hook diff --git a/.changeset/shy-jobs-protect.md b/.changeset/shy-jobs-protect.md new file mode 100644 index 000000000..c8892e305 --- /dev/null +++ b/.changeset/shy-jobs-protect.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main to dev diff --git a/.changeset/six-snails-carry.md b/.changeset/six-snails-carry.md new file mode 100644 index 000000000..bceeaab7f --- /dev/null +++ b/.changeset/six-snails-carry.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": minor +--- + +extend DocumentPage interface with isPublished property diff --git a/.changeset/slimy-cats-repeat.md b/.changeset/slimy-cats-repeat.md new file mode 100644 index 000000000..631a65142 --- /dev/null +++ b/.changeset/slimy-cats-repeat.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +fix: export useEnabledFeatures hook diff --git a/.changeset/smart-suns-type.md b/.changeset/smart-suns-type.md new file mode 100644 index 000000000..60097a512 --- /dev/null +++ b/.changeset/smart-suns-type.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": minor +--- + +feat(AppBridgeBlock): add `isAuthenticated` to `BlockContext` type diff --git a/.changeset/smooth-planets-pay.md b/.changeset/smooth-planets-pay.md new file mode 100644 index 000000000..1003ebd33 --- /dev/null +++ b/.changeset/smooth-planets-pay.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge main into dev diff --git a/.changeset/soft-mugs-peel.md b/.changeset/soft-mugs-peel.md new file mode 100644 index 000000000..b0a7bc5ef --- /dev/null +++ b/.changeset/soft-mugs-peel.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: add useEnabledFeatures diff --git a/.changeset/sour-cheetahs-buy.md b/.changeset/sour-cheetahs-buy.md new file mode 100644 index 000000000..0398a1580 --- /dev/null +++ b/.changeset/sour-cheetahs-buy.md @@ -0,0 +1,5 @@ +--- +"@frontify/guideline-themes": patch +--- + +refactor(GuidelineThemes): type cleanup diff --git a/.changeset/spicy-books-shout.md b/.changeset/spicy-books-shout.md new file mode 100644 index 000000000..28e101143 --- /dev/null +++ b/.changeset/spicy-books-shout.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Feat: adjust secure request response diff --git a/.changeset/stale-hairs-wash.md b/.changeset/stale-hairs-wash.md new file mode 100644 index 000000000..0d8972a0f --- /dev/null +++ b/.changeset/stale-hairs-wash.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": major +--- + +feat: first alpha release, removes all deprecated functions on the AppBridgeBlock diff --git a/.changeset/strange-radios-bow.md b/.changeset/strange-radios-bow.md new file mode 100644 index 000000000..1576b2898 --- /dev/null +++ b/.changeset/strange-radios-bow.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: Introduced a State for AppBridgeTheme diff --git a/.changeset/strong-tools-check.md b/.changeset/strong-tools-check.md new file mode 100644 index 000000000..de01291df --- /dev/null +++ b/.changeset/strong-tools-check.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Fix: add retry to subscription diff --git a/.changeset/tasty-coins-jam.md b/.changeset/tasty-coins-jam.md new file mode 100644 index 000000000..20795e461 --- /dev/null +++ b/.changeset/tasty-coins-jam.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: add useIsEditing hook diff --git a/.changeset/tasty-vans-cough.md b/.changeset/tasty-vans-cough.md new file mode 100644 index 000000000..6d1d7f302 --- /dev/null +++ b/.changeset/tasty-vans-cough.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: (AppBridgeTheme) documentNavigation in context and command diff --git a/.changeset/ten-guests-wash.md b/.changeset/ten-guests-wash.md new file mode 100644 index 000000000..6febbe912 --- /dev/null +++ b/.changeset/ten-guests-wash.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: adds state listener to allow push from parent diff --git a/.changeset/thirty-eggs-judge.md b/.changeset/thirty-eggs-judge.md new file mode 100644 index 000000000..777c3ba7f --- /dev/null +++ b/.changeset/thirty-eggs-judge.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +refactor: Removed `appBridgeTheme` from the `@frontify/app-bridge` package. It is now provided via the `@frontify/app-bridge-theme` package. diff --git a/.changeset/thirty-pears-lay.md b/.changeset/thirty-pears-lay.md new file mode 100644 index 000000000..9ef168f6a --- /dev/null +++ b/.changeset/thirty-pears-lay.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +Added a SubscribeMap type diff --git a/.changeset/three-avocados-push.md b/.changeset/three-avocados-push.md new file mode 100644 index 000000000..073519f5e --- /dev/null +++ b/.changeset/three-avocados-push.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: extend AppBridgeTheme Event types to include State diff --git a/.changeset/tough-nails-shout.md b/.changeset/tough-nails-shout.md new file mode 100644 index 000000000..c8892e305 --- /dev/null +++ b/.changeset/tough-nails-shout.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +chore: merge main to dev diff --git a/.changeset/tricky-boats-sip.md b/.changeset/tricky-boats-sip.md new file mode 100644 index 000000000..0d5054719 --- /dev/null +++ b/.changeset/tricky-boats-sip.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": minor +--- + +feat(AppBridgeBlock): adds `creationFormUri` to the type `template` diff --git a/.changeset/twelve-keys-begin.md b/.changeset/twelve-keys-begin.md new file mode 100644 index 000000000..5833dee06 --- /dev/null +++ b/.changeset/twelve-keys-begin.md @@ -0,0 +1,6 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat(AppBridgeTheme): added command to open/close AiBrandAssistant dialog +feat(AppBridgeTheme): added list of enabled features to context diff --git a/.changeset/violet-elephants-tan.md b/.changeset/violet-elephants-tan.md new file mode 100644 index 000000000..bce8889d1 --- /dev/null +++ b/.changeset/violet-elephants-tan.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat(AppBridgeTheme): add EventRegistry diff --git a/.changeset/warm-hounds-refuse.md b/.changeset/warm-hounds-refuse.md new file mode 100644 index 000000000..1003ebd33 --- /dev/null +++ b/.changeset/warm-hounds-refuse.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge main into dev diff --git a/.changeset/wild-bags-judge.md b/.changeset/wild-bags-judge.md new file mode 100644 index 000000000..e01123abd --- /dev/null +++ b/.changeset/wild-bags-judge.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": minor +--- + +feat: add navigateToDocumentSection command to AppBridgeTheme diff --git a/.changeset/wise-zoos-jam.md b/.changeset/wise-zoos-jam.md new file mode 100644 index 000000000..bdc68a761 --- /dev/null +++ b/.changeset/wise-zoos-jam.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": patch +--- + +feat: export platform apps dialog diff --git a/.changeset/witty-rivers-smile.md b/.changeset/witty-rivers-smile.md new file mode 100644 index 000000000..834e4e6ef --- /dev/null +++ b/.changeset/witty-rivers-smile.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +Merge `main` into `dev` diff --git a/.changeset/witty-tools-check.md b/.changeset/witty-tools-check.md new file mode 100644 index 000000000..dca04c628 --- /dev/null +++ b/.changeset/witty-tools-check.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge": patch +--- + +feat: deprecate react hooks for platform apps diff --git a/.changeset/young-crews-hope.md b/.changeset/young-crews-hope.md new file mode 100644 index 000000000..caf2feef7 --- /dev/null +++ b/.changeset/young-crews-hope.md @@ -0,0 +1,5 @@ +--- +"@frontify/app-bridge-theme": minor +--- + +feat(GuidelineSearchResult): added additional prop to `GuidelineSearchResult` diff --git a/.github/workflows/app-bridge-theme-continuous-integration.yml b/.github/workflows/app-bridge-theme-continuous-integration.yml new file mode 100644 index 000000000..5ce06046f --- /dev/null +++ b/.github/workflows/app-bridge-theme-continuous-integration.yml @@ -0,0 +1,46 @@ +name: App Bridge Theme CI + +on: + pull_request: + paths: + - packages/app-bridge-theme/** + +# Ensures that only one workflow per branch will run at a time. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + app-bridge-theme-ci: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout default branch + uses: actions/checkout@v4 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + + - name: Use pnpm + uses: pnpm/action-setup@v3 + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: Install npm dependencies + run: pnpm i --frozen-lockfile + + - name: Typecheck code + run: pnpm --stream --filter {packages/app-bridge-theme} typecheck + + - name: Lint code + run: pnpm --stream --filter {packages/app-bridge-theme} lint + + - name: Test code + run: pnpm --stream --filter {packages/app-bridge-theme} test:coverage diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 2eb4389da..5eba54c3a 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - dev concurrency: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/guideline-blocks-continuous-integration.yml b/.github/workflows/guideline-blocks-continuous-integration.yml index 66ba3f677..467ac4c02 100644 --- a/.github/workflows/guideline-blocks-continuous-integration.yml +++ b/.github/workflows/guideline-blocks-continuous-integration.yml @@ -18,9 +18,7 @@ jobs: strategy: fail-fast: false matrix: - app-bridge-version: - ["3.0.0", "workspace:^", "4.0.0-alpha.0", "^4.0.0-alpha.0"] - + app-bridge-version: ["3.0.0", "^3.0.0", "workspace:^"] steps: - name: Checkout default branch uses: actions/checkout@v4 @@ -64,8 +62,7 @@ jobs: strategy: fail-fast: false matrix: - app-bridge-version: - ["3.0.0", "workspace:^", "4.0.0-alpha.0", "^4.0.0-alpha.0"] + app-bridge-version: ["3.0.0", "^3.0.0", "workspace:^"] steps: - name: Checkout current commit @@ -116,8 +113,7 @@ jobs: strategy: fail-fast: false matrix: - app-bridge-version: - ["3.0.0", "workspace:^", "4.0.0-alpha.0", "^4.0.0-alpha.0"] + app-bridge-version: ["3.0.0", "^3.0.0", "workspace:^"] steps: - name: Checkout current commit diff --git a/.github/workflows/guideline-themes-continuous-integration.yml b/.github/workflows/guideline-themes-continuous-integration.yml new file mode 100644 index 000000000..88546bf3c --- /dev/null +++ b/.github/workflows/guideline-themes-continuous-integration.yml @@ -0,0 +1,82 @@ +name: Guideline Themes CI + +on: + pull_request: + paths: + - packages/guideline-themes/** + +# Ensures that only one workflow per branch will run at a time. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck: + runs-on: ubuntu-latest + timeout-minutes: 10 + + strategy: + fail-fast: false + matrix: + app-bridge-theme-version: ["workspace:^"] + + steps: + - name: Checkout default branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use pnpm + uses: pnpm/action-setup@v3 + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: Install npm dependencies + run: pnpm i --frozen-lockfile + + - name: Build @frontify/app-bridge-theme (if workspace) + if: matrix.app-bridge-theme-version == 'workspace:^' + run: pnpm build:app-bridge-theme + + - name: Add app-bridge-theme dependency + run: pnpm add @frontify/app-bridge-theme@${{ matrix.app-bridge-theme-version }} --filter {packages/guideline-themes} + + - name: Typecheck code + run: pnpm --stream --filter {packages/guideline-themes} typecheck + + - name: Lint code + run: pnpm --stream --filter {packages/guideline-themes} lint + + unit-tests: + runs-on: ubuntu-latest + timeout-minutes: 10 + + strategy: + fail-fast: false + + steps: + - name: Checkout current commit + uses: actions/checkout@v4 + + - name: Use pnpm + uses: pnpm/action-setup@v3 + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Tests + run: pnpm --stream --filter {packages/guideline-themes} test diff --git a/CODEOWNERS b/CODEOWNERS index a2d1bdc74..79bea3388 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,151 +2,9 @@ packages/app-bridge/src/types @Frontify/frontend-platform -## App Bridge Errors -packages/app-bridge/src/errors/HttpClientError.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations -packages/app-bridge/src/errors/InitializationError.ts @Frontify/apps-and-integrations -packages/app-bridge/src/errors/NotifyError.ts @Frontify/apps-and-integrations -packages/app-bridge/src/errors/TimeoutReachedError.ts @Frontify/apps-and-integrations -packages/app-bridge/src/errors/ValidationError.ts @Frontify/apps-and-integrations - -## App Bridge Helpers - -packages/app-bridge/src/helpers/ @Frontify/themes-and-navigation - -## React Hooks - -packages/app-bridge/src/react/useAssetBulkDownload.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetBulkDownload.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetChooser.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetChooser.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetUpload.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetViewer.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useAssetViewer.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockAssets.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockAssets.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockSettings.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockSettings.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockTemplates.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBlockTemplates.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useBrandportalLink.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useBrandportalLink.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useCategorizedDocumentPages.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useCategorizedDocumentPages.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useColorPalettes.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useColorPalettes.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useColors.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useColors.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useCoverPage.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useCoverPage.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentCategories.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentCategories.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentGroups.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentGroups.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentPageTargets.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentPageTargets.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentSection.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentTargets.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useDocumentTargets.tsx @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useEditorState.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useFileInput.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useFileUpload.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useGroupedDocuments.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useGroupedDocuments.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useGuidelineActions.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useGuidelineActions.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useLanguage.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useLanguage.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useNavigationManager.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useNavigationManager.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/usePageTemplateSettings.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/usePageTemplateSettings.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/usePlatformAppBridge.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/react/usePlatformAppBridge.ts @Frontify/apps-and-integrations -packages/app-bridge/src/react/usePrivacySettings.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/usePrivacySettings.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useReadyForPrint.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useReadyForPrint.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useTemplateAssets.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useTemplateAssets.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useTemplateChooser.spec.tsx @Frontify/content-and-blocks -packages/app-bridge/src/react/useTemplateChooser.ts @Frontify/content-and-blocks -packages/app-bridge/src/react/useThemeAssets.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useThemeAssets.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useThemeSettings.spec.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useThemeSettings.ts @Frontify/themes-and-navigation -packages/app-bridge/src/react/useUncategorizedDocumentPages.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useUncategorizedDocumentPages.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useUngroupedDocuments.spec.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/react/useUngroupedDocuments.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation - -### App Bridge registries api - -packages/app-bridge/src/registries/api/CreateAsset.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/CreateAsset.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/GetAssetResourceInformation.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/GetAssetResourceInformation.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/GetProxyNetworkCall.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/GetProxyNetworkCall.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/api/GetAssetBulkDownloadToken.ts @Frontify/content-and-blocks -packages/app-bridge/src/registries/api/GetCurrentUser.ts @Frontify/content-and-blocks @Frontify/apps-and-integrations @Frontify/themes-and-navigation -packages/app-bridge/src/registries/api/GetCurrentUser.spec.ts @Frontify/content-and-blocks @Frontify/apps-and-integrations @Frontify/themes-and-navigation - -### App Bridge registries commands -packages/app-bridge/src/registries/commands/AssetChooser.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation -packages/app-bridge/src/registries/commands/AssetViewer.ts @Frontify/content-and-blocks -packages/app-bridge/src/registries/commands/Connection.ts @Frontify/apps-and-integrations -packages/app-bridge/src/registries/commands/DownloadAsset.ts @Frontify/content-and-blocks -packages/app-bridge/src/registries/commands/NavigationManager.ts @Frontify/themes-and-navigation -packages/app-bridge/src/registries/commands/NewPublication.ts @Frontify/content-and-blocks -packages/app-bridge/src/registries/commands/TemplateChooser.ts @Frontify/content-and-blocks - -### App Bridge registries events - -packages/app-bridge/src/registries/events/ @Frontify/content-and-blocks @Frontify/themes-and-navigation - -### App Bridge registries verbs - -packages/app-bridge/src/registries/verbs/ @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations - -## App Bridge repositories - -packages/app-bridge/src/repositories/ @Frontify/content-and-blocks - -## App Bridge Tests - -packages/app-bridge/src/tests/AppBridgeBlockStub.ts @Frontify/content-and-blocks -packages/app-bridge/src/tests/AppBridgePlatformAppStub.ts @Frontify/apps-and-integrations -packages/app-bridge/src/tests/AppBridgeThemeStub.ts @Frontify/themes-and-navigation -packages/app-bridge/src/tests/withAppBridgeBlockStubs.tsx @Frontify/content-and-blocks -packages/app-bridge/src/tests/withAppBridgeThemeStubs.tsx @Frontify/themes-and-navigation - -## App Bridge Utilities - -packages/app-bridge/src/utilities/hash.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/httpClient.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/MessageBus.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/MessageBus.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/notify.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/object.spec.ts @Frontify/content-and-blocks -packages/app-bridge/src/utilities/object.ts @Frontify/content-and-blocks -packages/app-bridge/src/utilities/queryParams.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/queryParams.ts @Frontify/apps-and-integrations -packages/app-bridge/src/utilities/subscribe.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations - -## App Bridge Worker - -packages/app-bridge/src/worker/ @Frontify/content-and-blocks - -## App Bridge Interfaces - -packages/app-bridge/src/AppBridge.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations -packages/app-bridge/src/AppBridgeBlock.ts @Frontify/content-and-blocks -packages/app-bridge/src/AppBridgeCreateAsset.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/AppBridgeCreateAsset.ts @Frontify/apps-and-integrations -packages/app-bridge/src/AppBridgePlatformApp.spec.ts @Frontify/apps-and-integrations -packages/app-bridge/src/AppBridgePlatformApp.ts @Frontify/apps-and-integrations -packages/app-bridge/src/AppBridgeTheme.ts @Frontify/themes-and-navigation -packages/app-bridge/src/window.d.ts @Frontify/content-and-blocks @Frontify/themes-and-navigation @Frontify/apps-and-integrations +## App Bridge + +packages/app-bridge/ @Frontify/content-and-blocks # CLI @@ -183,4 +41,8 @@ packages/guideline-blocks-settings @Frontify/content-and-blocks packages/sidebar-settings @Frontify/content-and-blocks @Frontify/themes-and-navigation +# App Bridge Theme + +packages/app-bridge-theme @Frontify/themes-and-navigation + CODEOWNERS @Frontify/architecture diff --git a/package.json b/package.json index 9d77fbdce..cdb4acce4 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "private": true, "packageManager": "pnpm@9.1.3+sha512.7c2ea089e1a6af306409c4fc8c4f0897bdac32b772016196c469d9428f1fe2d5a21daf8ad6512762654ac645b5d9136bb210ec9a00afa8dbc4677843ba362ecd", "scripts": { - "build": "pnpm build:app-bridge && pnpm build:sidebar-settings && pnpm build:guideline-blocks-settings && pnpm build:platform-app && pnpm build:cli && pnpm build:app-bridge-app", + "build": "pnpm build:app-bridge && pnpm build:sidebar-settings && pnpm build:guideline-blocks-settings && pnpm build:platform-app && pnpm build:cli && pnpm build:app-bridge-theme && pnpm build:app-bridge-app && pnpm build:guideline-themes", "build:app-bridge": "pnpm --stream --filter {packages/app-bridge} build", + "build:app-bridge-theme": "pnpm --stream --filter {packages/app-bridge-theme} build", "build:app-bridge-app": "pnpm --stream --filter {packages/app-bridge-app} build", "build:sidebar-settings": "pnpm --stream --filter {packages/sidebar-settings} build", "build:guideline-blocks-settings": "pnpm --stream --filter {packages/guideline-blocks-settings} build", + "build:guideline-themes": "pnpm --stream --filter {packages/guideline-themes} build", "build:platform-app": "pnpm --stream --filter {packages/platform-app} build", "build:cli": "pnpm --stream --filter {packages/cli} build", "lint": "pnpm --stream --filter {packages/**} --parallel lint --quiet", diff --git a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts index 1caacc70c..2ab25ab63 100644 --- a/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts +++ b/packages/app-bridge-app/src/registries/api/ExecuteGraphQl.ts @@ -8,6 +8,7 @@ export type ExecuteGraphQlPayload = { * Attention: All Beta APIs can and will change without any warning. Be advised not to use those in production, see them as a preview of what is to come. */ beta?: boolean; + previewFeatures?: string[]; }; export type ExecuteGraphQlResponse = Record; diff --git a/packages/app-bridge-theme/.browserslistrc b/packages/app-bridge-theme/.browserslistrc new file mode 100644 index 000000000..d388ec78b --- /dev/null +++ b/packages/app-bridge-theme/.browserslistrc @@ -0,0 +1,3 @@ +last 2 versions +> 1% +not dead diff --git a/packages/app-bridge-theme/.eslintrc.cjs b/packages/app-bridge-theme/.eslintrc.cjs new file mode 100644 index 000000000..04ef1024d --- /dev/null +++ b/packages/app-bridge-theme/.eslintrc.cjs @@ -0,0 +1,58 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +module.exports = { + root: true, + extends: ['@frontify/eslint-config-react'], + plugins: ['notice'], + settings: { + react: { + version: 'detect', + }, + }, + parserOptions: { + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + overrides: [ + { + files: ['*.js', '*.ts', '*.tsx'], + rules: { + 'notice/notice': [ + 'error', + { + template: '/* (c) Copyright Frontify Ltd., all rights reserved. */\n\n', + messages: { + whenFailedToMatch: 'No Frontify copyright header set.', + }, + }, + ], + }, + }, + { + files: ['*.ts', '*.tsx', '*.mts', '*.cts'], + rules: { + 'no-prototype-builtins': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-misused-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/await-thenable': 'warn', + '@typescript-eslint/no-unsafe-enum-comparison': 'warn', + '@typescript-eslint/restrict-plus-operands': 'warn', + }, + }, + { + files: ['**/*.md/**/*'], + processor: 'markdown/markdown', + parserOptions: { + project: null, + }, + }, + ], +}; diff --git a/packages/app-bridge-theme/.gitignore b/packages/app-bridge-theme/.gitignore new file mode 100644 index 000000000..fc016d6fa --- /dev/null +++ b/packages/app-bridge-theme/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.idea +coverage +.DS_Store +package.json.d.ts diff --git a/packages/app-bridge-theme/.prettierrc.cjs b/packages/app-bridge-theme/.prettierrc.cjs new file mode 100644 index 000000000..ac04664a5 --- /dev/null +++ b/packages/app-bridge-theme/.prettierrc.cjs @@ -0,0 +1,10 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +module.exports = { + singleQuote: true, + tabWidth: 4, + printWidth: 120, + trailingComma: 'all', + arrowParens: 'always', + endOfLine: 'lf', +}; diff --git a/packages/app-bridge-theme/CHANGELOG.md b/packages/app-bridge-theme/CHANGELOG.md new file mode 100644 index 000000000..7a75feb05 --- /dev/null +++ b/packages/app-bridge-theme/CHANGELOG.md @@ -0,0 +1,174 @@ +# @frontify/app-bridge-theme + +## 0.0.0-alpha.27 + +### Patch Changes + +- [#1238](https://github.com/Frontify/brand-sdk/pull/1238) [`10712c0`](https://github.com/Frontify/brand-sdk/commit/10712c01e2b5b4979b8649525515e490b6c4125b) Thanks [@anxobotana](https://github.com/anxobotana)! - refactor: deprecating navigateToDocumentSection in favor of navigateToSectionHeading. + +## 0.0.0-alpha.26 + +### Patch Changes + +- [#1222](https://github.com/Frontify/brand-sdk/pull/1222) [`22226ab`](https://github.com/Frontify/brand-sdk/commit/22226ab1266c83a8206a36431a419ad215ecbcb6) Thanks [@mike85](https://github.com/mike85)! - feat: export platform apps dialog + +## 0.0.0-alpha.25 + +### Minor Changes + +- [#1214](https://github.com/Frontify/brand-sdk/pull/1214) [`242db22`](https://github.com/Frontify/brand-sdk/commit/242db22097bbb8f14187ef5af42d2cd6374a8357) Thanks [@ragi96](https://github.com/ragi96)! - feat: add command to open and close the platform apps dialog + +### Patch Changes + +- [#1218](https://github.com/Frontify/brand-sdk/pull/1218) [`e8f80db`](https://github.com/Frontify/brand-sdk/commit/e8f80db9ebff09c575acc932f22ce65886c4d7ee) Thanks [@ragi96](https://github.com/ragi96)! - feat: add `isPlatformAppsDialogOpen` to context + +## 0.0.0-alpha.24 + +### Patch Changes + +- [#1210](https://github.com/Frontify/brand-sdk/pull/1210) [`0324ffb`](https://github.com/Frontify/brand-sdk/commit/0324ffb2b108d33a83403cfd796c3445626f230a) Thanks [@Kenny806](https://github.com/Kenny806)! - feat: extend AppBridgeTheme Event types to include State + +## 0.0.0-alpha.23 + +### Patch Changes + +- [#1208](https://github.com/Frontify/brand-sdk/pull/1208) [`b8d3922`](https://github.com/Frontify/brand-sdk/commit/b8d3922f5e10445426e9c9930ab004e41d8c3fb7) Thanks [@Kenny806](https://github.com/Kenny806)! - feat: Introduced a State for AppBridgeTheme + +## 0.0.0-alpha.22 + +### Patch Changes + +- [#1203](https://github.com/Frontify/brand-sdk/pull/1203) [`265c4d4`](https://github.com/Frontify/brand-sdk/commit/265c4d48a76cf5bd6e257123f0d793bd930f91da) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: add activeSectionHeadingId to context + +## 0.0.0-alpha.21 + +### Minor Changes + +- [#1182](https://github.com/Frontify/brand-sdk/pull/1182) [`d3f4ac4`](https://github.com/Frontify/brand-sdk/commit/d3f4ac4385770dcfe6672dfd2f256f39d1473563) Thanks [@Kenny806](https://github.com/Kenny806)! - extend DocumentPage interface with isPublished property + +## 0.0.0-alpha.20 + +### Patch Changes + +- [#1110](https://github.com/Frontify/brand-sdk/pull/1110) [`18560db`](https://github.com/Frontify/brand-sdk/commit/18560dbd1bbfa3a76fdda0db5ed520b670c41979) Thanks [@mike85](https://github.com/mike85)! - feat(AppBridgeTheme): add EventRegistry + +## 0.0.0-alpha.19 + +### Patch Changes + +- [#1092](https://github.com/Frontify/brand-sdk/pull/1092) [`024b908`](https://github.com/Frontify/brand-sdk/commit/024b9089f68482aa908f08936c6a0c33cdaafb6c) Thanks [@anxobotana](https://github.com/anxobotana)! - refactor(appBridgeThemes): remove utilities folder + +## 0.0.0-alpha.18 + +### Patch Changes + +- [#1095](https://github.com/Frontify/brand-sdk/pull/1095) [`a70a9fe`](https://github.com/Frontify/brand-sdk/commit/a70a9fe0932e1a40c5d4d85e4fdcb3f008947b74) Thanks [@mike85](https://github.com/mike85)! - refactor(AppBridgeTheme): remove EventRegistry + +## 0.0.0-alpha.17 + +### Patch Changes + +- [#1084](https://github.com/Frontify/brand-sdk/pull/1084) [`3335235`](https://github.com/Frontify/brand-sdk/commit/3335235dc5f107280cac5c57a5008c4c9e2949e1) Thanks [@anxobotana](https://github.com/anxobotana)! - refactor(themes): add color utilities + +## 0.0.0-alpha.16 + +### Patch Changes + +- [#1075](https://github.com/Frontify/brand-sdk/pull/1075) [`772ed45`](https://github.com/Frontify/brand-sdk/commit/772ed451a39041e290597230f99de0727cd78b67) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - fix: export useEnabledFeatures hook + +## 0.0.0-alpha.15 + +### Patch Changes + +- [#1073](https://github.com/Frontify/brand-sdk/pull/1073) [`85f33ad`](https://github.com/Frontify/brand-sdk/commit/85f33ad945e8b1b59a289b1eb884f343a952116b) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - feat: add useEnabledFeatures + +## 0.0.0-alpha.14 + +### Patch Changes + +- [#1066](https://github.com/Frontify/brand-sdk/pull/1066) [`efcf755`](https://github.com/Frontify/brand-sdk/commit/efcf755dd1b0b5c2937e989445458f0e4dd992a6) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - feat(AppBridgeTheme): added command to open/close AiBrandAssistant dialog + feat(AppBridgeTheme): added list of enabled features to context + +## 0.0.0-alpha.13 + +### Patch Changes + +- [#1051](https://github.com/Frontify/brand-sdk/pull/1051) [`16ec9d1`](https://github.com/Frontify/brand-sdk/commit/16ec9d1ae5ced64bde74b275004cb632cba462b9) Thanks [@mike85](https://github.com/mike85)! - feat(AppBridgeTheme): add context hooks + +## 0.0.0-alpha.12 + +### Patch Changes + +- [#1046](https://github.com/Frontify/brand-sdk/pull/1046) [`facc3e6`](https://github.com/Frontify/brand-sdk/commit/facc3e647ce74bec3a8683889ebe493b88038e46) Thanks [@mike85](https://github.com/mike85)! - fix(AppBridgeTheme): update imports + +## 0.0.0-alpha.11 + +### Patch Changes + +- [#1031](https://github.com/Frontify/brand-sdk/pull/1031) [`54fd9e7`](https://github.com/Frontify/brand-sdk/commit/54fd9e7a4d973d7b47e67d5e7cc1c1309255e0ea) Thanks [@mike85](https://github.com/mike85)! - chore(AppBridgeTheme): add vitest to app bridge theme + +- [#1032](https://github.com/Frontify/brand-sdk/pull/1032) [`19346b8`](https://github.com/Frontify/brand-sdk/commit/19346b8a27cc1e2efb6cd6c61884496ddd67a34c) Thanks [@mike85](https://github.com/mike85)! - feat(AppBridgeTheme): add use language hook + +- [#1034](https://github.com/Frontify/brand-sdk/pull/1034) [`562990d`](https://github.com/Frontify/brand-sdk/commit/562990de5485cebebcb840ddb5abf74d1abe4357) Thanks [@mike85](https://github.com/mike85)! - feat: add useIsEditing hook + +- [#1040](https://github.com/Frontify/brand-sdk/pull/1040) [`6bcd595`](https://github.com/Frontify/brand-sdk/commit/6bcd595dacebd55520f1cf87bc8fcbf849cc80d2) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: (AppBridgeTheme) documentNavigation in context and command + +## 0.0.0-alpha.10 + +### Minor Changes + +- [#1015](https://github.com/Frontify/brand-sdk/pull/1015) [`77088a8`](https://github.com/Frontify/brand-sdk/commit/77088a85a7c181e5040d7a2738fb9fd7d95dfd1d) Thanks [@bojangles-m](https://github.com/bojangles-m)! - feat(GuidelineSearchResult): added additional prop to `GuidelineSearchResult` + +## 0.0.0-alpha.9 + +### Patch Changes + +- [#983](https://github.com/Frontify/brand-sdk/pull/983) [`57b2f90`](https://github.com/Frontify/brand-sdk/commit/57b2f90c8042e05774e57c1065fc86242f468f48) Thanks [@anxobotana](https://github.com/anxobotana)! - fix: AppBridgeTheme command typing + +## 0.0.0-alpha.8 + +### Patch Changes + +- [#981](https://github.com/Frontify/brand-sdk/pull/981) [`02cd695`](https://github.com/Frontify/brand-sdk/commit/02cd695c896847691eae43ed774637614ad3fd32) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: AppBridgeTheme update context keys + +## 0.0.0-alpha.7 + +### Patch Changes + +- [#978](https://github.com/Frontify/brand-sdk/pull/978) [`5eb0306`](https://github.com/Frontify/brand-sdk/commit/5eb030695cc105a3ed514a5b38840b015a8d85ac) Thanks [@Kenny806](https://github.com/Kenny806)! - feat: add a navigate command + +## 0.0.0-alpha.6 + +### Patch Changes + +- [#974](https://github.com/Frontify/brand-sdk/pull/974) [`508f51e`](https://github.com/Frontify/brand-sdk/commit/508f51e1de4d091f8761f4b7897940574d819eee) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: templateContext and NavigationItem types + +## 0.0.0-alpha.5 + +### Patch Changes + +- [#971](https://github.com/Frontify/brand-sdk/pull/971) [`08b3506`](https://github.com/Frontify/brand-sdk/commit/08b3506f1b8dba87fed1b6eb3f379bf619327d45) Thanks [@Kenny806](https://github.com/Kenny806)! - fix: exports + +## 0.0.0-alpha.4 + +### Patch Changes + +- [#967](https://github.com/Frontify/brand-sdk/pull/967) [`599df4f`](https://github.com/Frontify/brand-sdk/commit/599df4fd1db1ad43a8163538513c36a9ab3e938a) Thanks [@Kenny806](https://github.com/Kenny806)! - fix: extend AppBridgeThemeEvent to include assetsChosen + +## 0.0.0-alpha.3 + +### Patch Changes + +- [#965](https://github.com/Frontify/brand-sdk/pull/965) [`578e3e4`](https://github.com/Frontify/brand-sdk/commit/578e3e40025b1fbc88959181759257fe0e71d874) Thanks [@Kenny806](https://github.com/Kenny806)! - Added a SubscribeMap type + +## 0.0.0-alpha.2 + +### Patch Changes + +- [#963](https://github.com/Frontify/brand-sdk/pull/963) [`43472d5`](https://github.com/Frontify/brand-sdk/commit/43472d5f7ea4fd6bcdc44dc26103d1c3ce92cf4c) Thanks [@anxobotana](https://github.com/anxobotana)! - fix(AppBridgeThemes): fix build and codeowners + +## 0.0.0-alpha.1 + +### Patch Changes + +- [#960](https://github.com/Frontify/brand-sdk/pull/960) [`7a88fb5`](https://github.com/Frontify/brand-sdk/commit/7a88fb512a8209ab377ef12a70e2c8484d5b6799) Thanks [@anxobotana](https://github.com/anxobotana)! - feat(AppBridgeThemes): initial alpha release of AppBridgeThemes diff --git a/packages/app-bridge-theme/README.md b/packages/app-bridge-theme/README.md new file mode 100644 index 000000000..6ada156a8 --- /dev/null +++ b/packages/app-bridge-theme/README.md @@ -0,0 +1,2 @@ +# App Bridge Theme +Package to establish communication between Frontify and third party themes diff --git a/packages/app-bridge-theme/package.json b/packages/app-bridge-theme/package.json new file mode 100644 index 000000000..502fad8a0 --- /dev/null +++ b/packages/app-bridge-theme/package.json @@ -0,0 +1,64 @@ +{ + "name": "@frontify/app-bridge-theme", + "type": "module", + "version": "0.0.0-alpha.27", + "description": "Package to establish communication between Frontify and themes", + "author": "Frontify Developers ", + "repository": { + "type": "git", + "url": "https://github.com/Frontify/brand-sdk", + "directory": "packages/app-bridge-theme" + }, + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "package.json.d.ts" + ], + "engines": { + "node": ">=16" + }, + "scripts": { + "build": "ts-json-as-const ./package.json && vite build", + "dev": "vite build --watch", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "react": "^18" + }, + "dependencies": {}, + "devDependencies": { + "@frontify/eslint-config-react": "^0.17.6", + "@testing-library/react": "^14.3.0", + "@types/react": "^18.2.75", + "@types/react-dom": "^18.2.24", + "@vitest/coverage-v8": "^1.4.0", + "@vitest/ui": "^1.4.0", + "eslint": "^8.57.0", + "eslint-plugin-notice": "^0.9.10", + "happy-dom": "^13.10.1", + "prettier": "^3.2.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "ts-json-as-const": "^1.0.7", + "type-fest": "^4.15.0", + "typescript": "^5.4.4", + "vite": "^5.2.10", + "vite-plugin-dts": "^3.8.1", + "vitest": "^1.4.0" + } +} diff --git a/packages/app-bridge-theme/src/AppBridgeTheme.ts b/packages/app-bridge-theme/src/AppBridgeTheme.ts new file mode 100644 index 000000000..4c6880004 --- /dev/null +++ b/packages/app-bridge-theme/src/AppBridgeTheme.ts @@ -0,0 +1,42 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type CommandRegistry } from './registries'; +import { + type AppBridgeThemeEvent, + type Context, + type ContextReturn, + type DispatchHandlerParameter, + type EventCallbackParameter, + type EventNameParameter, + type EventUnsubscribeFunction, + type GuidelineSearchResult, + type State, + type StateReturn, +} from './types'; + +export interface AppBridgeTheme { + dispatch( + dispatchHandler: DispatchHandlerParameter, + ): Promise; + + context(): ContextReturn; + context(key: Key): ContextReturn; + context(key?: keyof Context | void): unknown; + + state(): StateReturn; + state(key: Key): StateReturn; + state(key?: keyof State | void): unknown; + + subscribe( + eventName: EventNameParameter, + callback: EventCallbackParameter, + ): EventUnsubscribeFunction; + + /** + * @deprecated + * Search in the current Guideline for a given query. + * @param query - The query to search for. + * @param order - The order in which the results should be returned. Defaults to 'relevance'. + */ + searchInGuideline(query: string, order?: 'relevance' | 'newest' | 'oldest'): Promise; +} diff --git a/packages/app-bridge-theme/src/global.d.ts b/packages/app-bridge-theme/src/global.d.ts new file mode 100644 index 000000000..fddff887f --- /dev/null +++ b/packages/app-bridge-theme/src/global.d.ts @@ -0,0 +1,3 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +type Nullable = T | null; diff --git a/packages/app-bridge-theme/src/index.ts b/packages/app-bridge-theme/src/index.ts new file mode 100644 index 000000000..4ad8c9eff --- /dev/null +++ b/packages/app-bridge-theme/src/index.ts @@ -0,0 +1,6 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './AppBridgeTheme'; +export * from './react'; +export * from './registries'; +export * from './types'; diff --git a/packages/app-bridge-theme/src/react/index.ts b/packages/app-bridge-theme/src/react/index.ts new file mode 100644 index 000000000..5029be10b --- /dev/null +++ b/packages/app-bridge-theme/src/react/index.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './useDocumentNavigation'; +export * from './useIsEditing'; +export * from './useCurrentLanguage'; +export * from './useDefaultLanguage'; +export * from './useActiveSectionHeadingId'; +export * from './useLanguages'; +export * from './usePortalNavigation'; +export * from './useSettings'; +export * from './useTemplateContext'; +export * from './useEnabledFeatures'; diff --git a/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.spec.ts b/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.spec.ts new file mode 100644 index 000000000..6681bb5e7 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.spec.ts @@ -0,0 +1,81 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useActiveSectionHeadingId } from './useActiveSectionHeadingId'; + +const UPDATED_HEADING_ID = 321; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(null), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useActiveSectionHeadingId', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with ActiveSectionHeadingId', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useActiveSectionHeadingId(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('activeSectionHeadingId'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useActiveSectionHeadingId(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial ActiveSectionHeadingId', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useActiveSectionHeadingId(appBridgeTheme)); + + expect(result.current).toBeNull(); + }); + + it('should update the ActiveSectionHeadingId on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useActiveSectionHeadingId(appBridgeTheme)); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_HEADING_ID); + stubs.subscribeStub.mock.calls[0][0](UPDATED_HEADING_ID); + } + }); + + expect(result.current).toEqual(UPDATED_HEADING_ID); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useActiveSectionHeadingId(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.ts b/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.ts new file mode 100644 index 000000000..f3251283c --- /dev/null +++ b/packages/app-bridge-theme/src/react/useActiveSectionHeadingId.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme.ts'; + +export const useActiveSectionHeadingId = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore( + appBridge.context('activeSectionHeadingId').subscribe, + appBridge.context('activeSectionHeadingId').get, + ); +}; diff --git a/packages/app-bridge-theme/src/react/useCurrentLanguage.spec.ts b/packages/app-bridge-theme/src/react/useCurrentLanguage.spec.ts new file mode 100644 index 000000000..5f9c16fea --- /dev/null +++ b/packages/app-bridge-theme/src/react/useCurrentLanguage.spec.ts @@ -0,0 +1,82 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useCurrentLanguage } from './useCurrentLanguage'; + +const INITIAL_LANGUAGE = 'de'; +const UPDATED_LANGUAGE = 'es'; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_LANGUAGE), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useCurrentLanguage', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with currentLanguage', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useCurrentLanguage(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('currentLanguage'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useCurrentLanguage(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial currentLanguage', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useCurrentLanguage(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_LANGUAGE); + }); + + it('should update the currentLanguage on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useCurrentLanguage(appBridgeTheme)); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_LANGUAGE); + stubs.subscribeStub.mock.calls[0][0](UPDATED_LANGUAGE); + } + }); + + expect(result.current).toEqual(UPDATED_LANGUAGE); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useCurrentLanguage(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useCurrentLanguage.ts b/packages/app-bridge-theme/src/react/useCurrentLanguage.ts new file mode 100644 index 000000000..c54ee4346 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useCurrentLanguage.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme.ts'; + +export const useCurrentLanguage = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore( + appBridge.context('currentLanguage').subscribe, + appBridge.context('currentLanguage').get, + ); +}; diff --git a/packages/app-bridge-theme/src/react/useDefaultLanguage.spec.ts b/packages/app-bridge-theme/src/react/useDefaultLanguage.spec.ts new file mode 100644 index 000000000..0f93a4fdd --- /dev/null +++ b/packages/app-bridge-theme/src/react/useDefaultLanguage.spec.ts @@ -0,0 +1,82 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useDefaultLanguage } from './useDefaultLanguage'; + +const INITIAL_LANGUAGE = 'de'; +const UPDATED_LANGUAGE = 'es'; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_LANGUAGE), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useDefaultLanguage', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with defaultLanguage', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useDefaultLanguage(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('defaultLanguage'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useDefaultLanguage(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial defaultLanguage', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useDefaultLanguage(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_LANGUAGE); + }); + + it('should update the defaultLanguage on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useDefaultLanguage(appBridgeTheme)); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_LANGUAGE); + stubs.subscribeStub.mock.calls[0][0](UPDATED_LANGUAGE); + } + }); + + expect(result.current).toEqual(UPDATED_LANGUAGE); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useDefaultLanguage(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useDefaultLanguage.ts b/packages/app-bridge-theme/src/react/useDefaultLanguage.ts new file mode 100644 index 000000000..58c9d780c --- /dev/null +++ b/packages/app-bridge-theme/src/react/useDefaultLanguage.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme.ts'; + +export const useDefaultLanguage = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore( + appBridge.context('defaultLanguage').subscribe, + appBridge.context('defaultLanguage').get, + ); +}; diff --git a/packages/app-bridge-theme/src/react/useDocumentNavigation.spec.ts b/packages/app-bridge-theme/src/react/useDocumentNavigation.spec.ts new file mode 100644 index 000000000..0035ed9f6 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useDocumentNavigation.spec.ts @@ -0,0 +1,145 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; +import { + type DocumentPageNavigationItem, + type DocumentChildNavigationItem, + type DocumentNavigationItem, +} from '../types'; + +import { useDocumentNavigation } from './useDocumentNavigation'; + +const DocumentPageDummy = (id: number) => + ({ + type: 'document', + + id: () => { + return id; + }, + slug: (language: string) => { + return `dummy-page-slug-${id}-${language}`; + }, + + title: (language: string) => { + return `Dummy Page title - ${id} - ${language}`; + }, + + url: (language: string) => { + return `page-${id}-${language}`; + }, + }) as unknown as DocumentPageNavigationItem; + +const DocumentDummy = (id: number, children: DocumentChildNavigationItem[]) => + ({ + type: 'document', + + id: () => { + return id; + }, + + children: () => { + return children; + }, + + slug: (language: string) => { + return `dummy-doc-slug-${id}-${language}`; + }, + + title: (language: string) => { + return `Dummy Doc title - ${id} - ${language}`; + }, + + url: (language: string) => { + return `https://blah/doc/${id}/${language}`; + }, + }) as unknown as DocumentNavigationItem; + +const DOCUMENT_ID = 120; +const DOCUMENT_ID_2 = 130; +const DOCUMENT_PAGE_ID = 121; +const DOCUMENT_PAGE_ID_2 = 131; + +const DOCUMENT_DUMMY = DocumentDummy(DOCUMENT_ID, [DocumentPageDummy(DOCUMENT_PAGE_ID)]); +const DOCUMENT_DUMMY_2 = DocumentDummy(DOCUMENT_ID_2, [DocumentPageDummy(DOCUMENT_PAGE_ID_2)]); +const DOCUMENT_NAVIGATION = { + [DOCUMENT_ID]: [DocumentPageDummy(DOCUMENT_PAGE_ID)], +}; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), + dispatch: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(DOCUMENT_NAVIGATION), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + dispatch: stubs.dispatch, + }) as unknown as AppBridgeTheme; + +describe('useDocumentNavigation', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with DocumentNavigation', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useDocumentNavigation(appBridgeTheme, DOCUMENT_DUMMY)); + + expect(stubs.contextStub).toHaveBeenCalled(); + expect(stubs.dispatch).toHaveBeenCalled(); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useDocumentNavigation(appBridgeTheme, DOCUMENT_DUMMY)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial DocumentNavigation state', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useDocumentNavigation(appBridgeTheme, DOCUMENT_DUMMY)); + + expect(result.current).toEqual(DOCUMENT_NAVIGATION[DOCUMENT_ID]); + }); + + it('should update the DocumentNavigation state on change', async () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useDocumentNavigation(appBridgeTheme, DOCUMENT_DUMMY)); + + expect(result.current).toEqual(DOCUMENT_NAVIGATION[DOCUMENT_ID]); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(DOCUMENT_DUMMY_2); + stubs.subscribeStub.mock.calls[0][0](DOCUMENT_DUMMY_2); + } + }); + + expect(stubs.dispatch).toHaveBeenCalled(); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useDocumentNavigation(appBridgeTheme, DOCUMENT_DUMMY)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useDocumentNavigation.ts b/packages/app-bridge-theme/src/react/useDocumentNavigation.ts new file mode 100644 index 000000000..8290f72da --- /dev/null +++ b/packages/app-bridge-theme/src/react/useDocumentNavigation.ts @@ -0,0 +1,20 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useEffect, useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme.ts'; +import { hydrateContextDocumentNavigation } from '../registries/commands/HydrateContextDocumentNavigation'; +import { type DocumentNavigationItem, type DocumentChildNavigationItem } from '../types/Guideline'; + +export const useDocumentNavigation = (appBridge: AppBridgeTheme, document: DocumentNavigationItem) => { + const documentNavigation: Record = useSyncExternalStore( + appBridge.context('documentNavigation').subscribe, + appBridge.context('documentNavigation').get, + ); + + useEffect(() => { + appBridge.dispatch(hydrateContextDocumentNavigation(document.id())); + }, [appBridge, document]); + + return documentNavigation ? documentNavigation[document.id()] ?? [] : []; +}; diff --git a/packages/app-bridge-theme/src/react/useEnabledFeatures.spec.ts b/packages/app-bridge-theme/src/react/useEnabledFeatures.spec.ts new file mode 100644 index 000000000..0d3b96d03 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useEnabledFeatures.spec.ts @@ -0,0 +1,84 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useEnabledFeatures } from './useEnabledFeatures'; + +const INITIAL_ENABLED_FEATURES: string[] = []; +const UPDATED_ENABLED_FEATURES = ['feature1', 'feature2']; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_ENABLED_FEATURES), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useEnabledFeatures', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with enabledFeatures', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useEnabledFeatures(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('enabledFeatures'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useEnabledFeatures(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial enabled features', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useEnabledFeatures(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_ENABLED_FEATURES); + }); + + it('should update the enabled features on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useEnabledFeatures(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_ENABLED_FEATURES); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_ENABLED_FEATURES); + stubs.subscribeStub.mock.calls[0][0](UPDATED_ENABLED_FEATURES); + } + }); + + expect(result.current).toEqual(UPDATED_ENABLED_FEATURES); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useEnabledFeatures(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useEnabledFeatures.ts b/packages/app-bridge-theme/src/react/useEnabledFeatures.ts new file mode 100644 index 000000000..28372e980 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useEnabledFeatures.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +export const useEnabledFeatures = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore( + appBridge.context('enabledFeatures').subscribe, + appBridge.context('enabledFeatures').get, + ); +}; diff --git a/packages/app-bridge-theme/src/react/useIsEditing.spec.ts b/packages/app-bridge-theme/src/react/useIsEditing.spec.ts new file mode 100644 index 000000000..0ef5e3a5d --- /dev/null +++ b/packages/app-bridge-theme/src/react/useIsEditing.spec.ts @@ -0,0 +1,84 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useIsEditing } from './useIsEditing'; + +const INITIAL_EDITOR_STATE = false; +const UPDATED_EDITOR_STATE = true; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_EDITOR_STATE), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useIsEditing', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with isEditing', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useIsEditing(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('isEditing'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useIsEditing(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial editor state', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useIsEditing(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_EDITOR_STATE); + }); + + it('should update the editor state on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useIsEditing(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_EDITOR_STATE); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_EDITOR_STATE); + stubs.subscribeStub.mock.calls[0][0](UPDATED_EDITOR_STATE); + } + }); + + expect(result.current).toEqual(UPDATED_EDITOR_STATE); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useIsEditing(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useIsEditing.ts b/packages/app-bridge-theme/src/react/useIsEditing.ts new file mode 100644 index 000000000..b401c7156 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useIsEditing.ts @@ -0,0 +1,9 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +export const useIsEditing = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore(appBridge.context('isEditing').subscribe, appBridge.context('isEditing').get); +}; diff --git a/packages/app-bridge-theme/src/react/useLanguages.spec.ts b/packages/app-bridge-theme/src/react/useLanguages.spec.ts new file mode 100644 index 000000000..f0e7c9789 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useLanguages.spec.ts @@ -0,0 +1,116 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useLanguages } from './useLanguages'; + +const INITIAL_LANGUAGES = [ + { + isoCode: 'en', + name: 'English', + isDefault: true, + isDraft: false, + }, + { + isoCode: 'de', + name: 'Deutsch', + isDefault: false, + isDraft: false, + }, +]; +const UPDATED_LANGUAGES = [ + { + isoCode: 'en', + name: 'English', + isDefault: false, + isDraft: false, + }, + { + isoCode: 'de', + name: 'Deutsch', + isDefault: true, + isDraft: false, + }, + { + isoCode: 'fr', + name: 'Français', + isDefault: false, + isDraft: true, + }, +]; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_LANGUAGES), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useLanguages', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with languages', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useLanguages(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('languages'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useLanguages(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial languages state', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useLanguages(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_LANGUAGES); + }); + + it('should update the languages state on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useLanguages(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_LANGUAGES); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_LANGUAGES); + stubs.subscribeStub.mock.calls[0][0](UPDATED_LANGUAGES); + } + }); + + expect(result.current).toEqual(UPDATED_LANGUAGES); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useLanguages(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useLanguages.ts b/packages/app-bridge-theme/src/react/useLanguages.ts new file mode 100644 index 000000000..7f2967648 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useLanguages.ts @@ -0,0 +1,9 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme.ts'; + +export const useLanguages = (appBridge: AppBridgeTheme) => { + return useSyncExternalStore(appBridge.context('languages').subscribe, appBridge.context('languages').get); +}; diff --git a/packages/app-bridge-theme/src/react/usePortalNavigation.spec.ts b/packages/app-bridge-theme/src/react/usePortalNavigation.spec.ts new file mode 100644 index 000000000..11097b527 --- /dev/null +++ b/packages/app-bridge-theme/src/react/usePortalNavigation.spec.ts @@ -0,0 +1,94 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; +import { type DocumentNavigationItem } from '../types'; + +import { usePortalNavigation } from './usePortalNavigation'; + +const DocumentDummy = (id: number) => + ({ + type: 'document', + + id: () => { + return id; + }, + }) as unknown as DocumentNavigationItem; + +const INITIAL_PORTAL_NAVIGATION = [DocumentDummy(532), DocumentDummy(682), DocumentDummy(746)]; +const UPDATED_PORTAL_NAVIGATION = [DocumentDummy(8425), DocumentDummy(9634)]; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_PORTAL_NAVIGATION), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('usePortalNavigation', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with portalNavigation', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => usePortalNavigation(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('portalNavigation'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => usePortalNavigation(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial portalNavigation state', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => usePortalNavigation(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_PORTAL_NAVIGATION); + }); + + it('should update the portalNavigation state on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => usePortalNavigation(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_PORTAL_NAVIGATION); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_PORTAL_NAVIGATION); + stubs.subscribeStub.mock.calls[0][0](UPDATED_PORTAL_NAVIGATION); + } + }); + + expect(result.current).toEqual(UPDATED_PORTAL_NAVIGATION); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => usePortalNavigation(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/usePortalNavigation.ts b/packages/app-bridge-theme/src/react/usePortalNavigation.ts new file mode 100644 index 000000000..2588c220a --- /dev/null +++ b/packages/app-bridge-theme/src/react/usePortalNavigation.ts @@ -0,0 +1,14 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +export const usePortalNavigation = (appBridge: AppBridgeTheme) => { + const portalNavigation = useSyncExternalStore( + appBridge.context('portalNavigation').subscribe, + appBridge.context('portalNavigation').get, + ); + + return portalNavigation ?? []; +}; diff --git a/packages/app-bridge-theme/src/react/useSettings.spec.ts b/packages/app-bridge-theme/src/react/useSettings.spec.ts new file mode 100644 index 000000000..6094f552c --- /dev/null +++ b/packages/app-bridge-theme/src/react/useSettings.spec.ts @@ -0,0 +1,84 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +import { useSettings } from './useSettings'; + +const INITIAL_SETTINGS = { templateSettings: { color: 'red' }, templateAssets: { logo: [] } }; +const UPDATED_SETTINGS = { templateSettings: { color: 'blue', isSticky: false }, templateAssets: {} }; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_SETTINGS), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useSettings', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with settings', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useSettings(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('settings'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useSettings(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial settings', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useSettings(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_SETTINGS); + }); + + it('should update the settings on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useSettings(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_SETTINGS); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_SETTINGS); + stubs.subscribeStub.mock.calls[0][0](UPDATED_SETTINGS); + } + }); + + expect(result.current).toEqual(UPDATED_SETTINGS); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useSettings(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useSettings.ts b/packages/app-bridge-theme/src/react/useSettings.ts new file mode 100644 index 000000000..6253ad274 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useSettings.ts @@ -0,0 +1,13 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; + +export const useSettings = >(appBridge: AppBridgeTheme) => { + const settings = useSyncExternalStore(appBridge.context('settings').subscribe, appBridge.context('settings').get); + + const templateSettings = settings.templateSettings as T; + + return { templateSettings, templateAssets: settings.templateAssets }; +}; diff --git a/packages/app-bridge-theme/src/react/useTemplateContext.spec.ts b/packages/app-bridge-theme/src/react/useTemplateContext.spec.ts new file mode 100644 index 000000000..165b54093 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useTemplateContext.spec.ts @@ -0,0 +1,107 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; +import { type CoverPage, type DocumentLibrary, type TemplateContext } from '../types'; + +import { useTemplateContext } from './useTemplateContext'; + +const CoverPageDummy = (id: number) => + ({ + type: 'cover', + + id: () => { + return id; + }, + }) as unknown as CoverPage; + +const DocumentLibraryDummy = (id: number) => + ({ + type: 'document-library', + + id: () => { + return id; + }, + }) as unknown as DocumentLibrary; + +const INITIAL_TEMPLATE: TemplateContext = { type: 'cover', coverPage: CoverPageDummy(56), templateId: 'default' }; +const UPDATED_TEMPLATE: TemplateContext = { + type: 'library', + document: DocumentLibraryDummy(78), + templateId: 'default', +}; + +const stubs = vi.hoisted(() => ({ + contextStub: vi.fn(), + contextGetStub: vi.fn(), + subscribeStub: vi.fn(), + unsubscribeObserverStub: vi.fn(), +})); + +const stubbedAppBridgeTheme = () => + ({ + context: stubs.contextStub.mockReturnValue({ + get: stubs.contextGetStub.mockReturnValue(INITIAL_TEMPLATE), + subscribe: stubs.subscribeStub.mockReturnValue(stubs.unsubscribeObserverStub), + }), + }) as unknown as AppBridgeTheme; + +describe('useTemplateContext', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call the context with template', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useTemplateContext(appBridgeTheme)); + + expect(stubs.contextStub).toHaveBeenCalledWith('template'); + }); + + it('should subscribe to context updates', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + renderHook(() => useTemplateContext(appBridgeTheme)); + + expect(stubs.subscribeStub).toHaveBeenCalledOnce(); + }); + + it('should return the correct initial template', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useTemplateContext(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_TEMPLATE); + }); + + it('should update the template on change', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { result } = renderHook(() => useTemplateContext(appBridgeTheme)); + + expect(result.current).toEqual(INITIAL_TEMPLATE); + + act(() => { + if (vi.isMockFunction(stubs.subscribeStub)) { + stubs.contextGetStub.mockReturnValue(UPDATED_TEMPLATE); + stubs.subscribeStub.mock.calls[0][0](UPDATED_TEMPLATE); + } + }); + + expect(result.current).toEqual(UPDATED_TEMPLATE); + }); + + it('should unsubscribe on unmount', () => { + const appBridgeTheme = stubbedAppBridgeTheme(); + + const { unmount } = renderHook(() => useTemplateContext(appBridgeTheme)); + + unmount(); + + expect(stubs.unsubscribeObserverStub).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/app-bridge-theme/src/react/useTemplateContext.ts b/packages/app-bridge-theme/src/react/useTemplateContext.ts new file mode 100644 index 000000000..963735e49 --- /dev/null +++ b/packages/app-bridge-theme/src/react/useTemplateContext.ts @@ -0,0 +1,10 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { useSyncExternalStore } from 'react'; + +import { type AppBridgeTheme } from '../AppBridgeTheme'; +import { type TemplateContext } from '../types'; + +export const useTemplateContext = (appBridge: AppBridgeTheme): TemplateContext | null => { + return useSyncExternalStore(appBridge.context('template').subscribe, appBridge.context('template').get); +}; diff --git a/packages/app-bridge-theme/src/registries/CommandRegistry.ts b/packages/app-bridge-theme/src/registries/CommandRegistry.ts new file mode 100644 index 000000000..ab58bf232 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/CommandRegistry.ts @@ -0,0 +1,26 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type Simplify } from 'type-fest'; + +import { type ObjectNameValidator } from '../types'; + +export type CommandRegistry = CommandNameValidator<{ + openPlatformAppsDialog: void; + closePlatformAppsDialog: void; + openSearchDialog: void; + closeSearchDialog: void; + openAiBrandAssistantDialog: void; + closeAiBrandAssistantDialog: void; + navigate: string; + navigateToDocumentSection: number | string; + navigateToSectionHeading: number | string; + hydrateContextDocumentNavigation: number; +}>; + +type CommandNameValidator = Simplify< + ObjectNameValidator +>; + +type CommandNamePattern = { [commandName: `${CommandVerb}${string}`]: unknown }; + +type CommandVerb = 'open' | 'close' | 'navigate' | 'hydrateContext'; diff --git a/packages/app-bridge-theme/src/registries/EventRegistry.ts b/packages/app-bridge-theme/src/registries/EventRegistry.ts new file mode 100644 index 000000000..3f541f39d --- /dev/null +++ b/packages/app-bridge-theme/src/registries/EventRegistry.ts @@ -0,0 +1,7 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type Asset, type EventNameValidator } from '../types'; + +export type EventRegistry = EventNameValidator<{ + assetsChosen: { assets: Asset[] }; +}>; diff --git a/packages/app-bridge-theme/src/registries/commands/AiBrandAssistantDialog.ts b/packages/app-bridge-theme/src/registries/commands/AiBrandAssistantDialog.ts new file mode 100644 index 000000000..d812a1ded --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/AiBrandAssistantDialog.ts @@ -0,0 +1,14 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const openAiBrandAssistantDialog = (): DispatchHandlerParameter< + 'openAiBrandAssistantDialog', + CommandRegistry +> => ({ name: 'openAiBrandAssistantDialog' }); + +export const closeAiBrandAssistantDialog = (): DispatchHandlerParameter< + 'closeAiBrandAssistantDialog', + CommandRegistry +> => ({ name: 'closeAiBrandAssistantDialog' }); diff --git a/packages/app-bridge-theme/src/registries/commands/HydrateContextDocumentNavigation.ts b/packages/app-bridge-theme/src/registries/commands/HydrateContextDocumentNavigation.ts new file mode 100644 index 000000000..932b64904 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/HydrateContextDocumentNavigation.ts @@ -0,0 +1,11 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const hydrateContextDocumentNavigation = ( + documentId: CommandRegistry['hydrateContextDocumentNavigation'], +): DispatchHandlerParameter<'hydrateContextDocumentNavigation', CommandRegistry> => ({ + name: 'hydrateContextDocumentNavigation', + payload: documentId, +}); diff --git a/packages/app-bridge-theme/src/registries/commands/Navigate.ts b/packages/app-bridge-theme/src/registries/commands/Navigate.ts new file mode 100644 index 000000000..e2b0333fa --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/Navigate.ts @@ -0,0 +1,9 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const navigate = (path: CommandRegistry['navigate']): DispatchHandlerParameter<'navigate', CommandRegistry> => ({ + name: 'navigate', + payload: path, +}); diff --git a/packages/app-bridge/src/registries/commands/NavigateToDocumentSection.ts b/packages/app-bridge-theme/src/registries/commands/NavigateToDocumentSection.ts similarity index 63% rename from packages/app-bridge/src/registries/commands/NavigateToDocumentSection.ts rename to packages/app-bridge-theme/src/registries/commands/NavigateToDocumentSection.ts index d75f6f62c..32353bd01 100644 --- a/packages/app-bridge/src/registries/commands/NavigateToDocumentSection.ts +++ b/packages/app-bridge-theme/src/registries/commands/NavigateToDocumentSection.ts @@ -1,9 +1,9 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ -import { type DispatchHandlerParameter } from '../../AppBridge'; - -import { type CommandRegistry } from './CommandRegistry'; +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; +/** @deprecated Use navigateToSectionHeading instead */ export const navigateToDocumentSection = ( sectionId: CommandRegistry['navigateToDocumentSection'], ): DispatchHandlerParameter<'navigateToDocumentSection', CommandRegistry> => ({ diff --git a/packages/app-bridge-theme/src/registries/commands/NavigateToSectionHeading.ts b/packages/app-bridge-theme/src/registries/commands/NavigateToSectionHeading.ts new file mode 100644 index 000000000..901ae7bd5 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/NavigateToSectionHeading.ts @@ -0,0 +1,11 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const navigateToSectionHeading = ( + sectionId: CommandRegistry['navigateToSectionHeading'], +): DispatchHandlerParameter<'navigateToSectionHeading', CommandRegistry> => ({ + name: 'navigateToSectionHeading', + payload: sectionId, +}); diff --git a/packages/app-bridge-theme/src/registries/commands/PlatformAppsDialog.ts b/packages/app-bridge-theme/src/registries/commands/PlatformAppsDialog.ts new file mode 100644 index 000000000..efcf87792 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/PlatformAppsDialog.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const openPlatformAppsDialog = (): DispatchHandlerParameter<'openPlatformAppsDialog', CommandRegistry> => ({ + name: 'openPlatformAppsDialog', +}); + +export const closePlatformAppsDialog = (): DispatchHandlerParameter<'closePlatformAppsDialog', CommandRegistry> => ({ + name: 'closePlatformAppsDialog', +}); diff --git a/packages/app-bridge-theme/src/registries/commands/SearchDialog.ts b/packages/app-bridge-theme/src/registries/commands/SearchDialog.ts new file mode 100644 index 000000000..ec4adbb07 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/SearchDialog.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type DispatchHandlerParameter } from '../../types'; +import { type CommandRegistry } from '../CommandRegistry'; + +export const openSearchDialog = (): DispatchHandlerParameter<'openSearchDialog', CommandRegistry> => ({ + name: 'openSearchDialog', +}); + +export const closeSearchDialog = (): DispatchHandlerParameter<'closeSearchDialog', CommandRegistry> => ({ + name: 'closeSearchDialog', +}); diff --git a/packages/app-bridge-theme/src/registries/commands/index.ts b/packages/app-bridge-theme/src/registries/commands/index.ts new file mode 100644 index 000000000..46736af73 --- /dev/null +++ b/packages/app-bridge-theme/src/registries/commands/index.ts @@ -0,0 +1,9 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './AiBrandAssistantDialog'; +export * from './SearchDialog'; +export * from './PlatformAppsDialog'; +export * from './Navigate'; +export * from './NavigateToDocumentSection'; +export * from './NavigateToSectionHeading'; +export * from './HydrateContextDocumentNavigation'; diff --git a/packages/app-bridge-theme/src/registries/index.ts b/packages/app-bridge-theme/src/registries/index.ts new file mode 100644 index 000000000..983ed724d --- /dev/null +++ b/packages/app-bridge-theme/src/registries/index.ts @@ -0,0 +1,5 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './CommandRegistry'; +export * from './EventRegistry'; +export * from './commands'; diff --git a/packages/app-bridge-theme/src/tests/setupTests.ts b/packages/app-bridge-theme/src/tests/setupTests.ts new file mode 100644 index 000000000..45eb936cf --- /dev/null +++ b/packages/app-bridge-theme/src/tests/setupTests.ts @@ -0,0 +1,8 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { configure } from '@testing-library/react'; +import { beforeAll } from 'vitest'; + +beforeAll(() => { + configure({ testIdAttribute: 'data-test-id' }); +}); diff --git a/packages/app-bridge-theme/src/types/Asset.ts b/packages/app-bridge-theme/src/types/Asset.ts new file mode 100644 index 000000000..385c92f15 --- /dev/null +++ b/packages/app-bridge-theme/src/types/Asset.ts @@ -0,0 +1,26 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export type Asset = { + id: number; + creatorName: string; + extension: string; + externalUrl: Nullable; + fileName: string; + title: string; + status: string; + objectType: string; + height: Nullable; + width: Nullable; + genericUrl: string; + previewUrl: string; + originUrl: string; + projectId: number; + fileSize: number; + fileSizeHumanReadable: string; + fileId: string; + token: string; + projectType: Nullable; + revisionId: Nullable; + backgroundColor: Nullable; + isDownloadProtected: boolean; +}; diff --git a/packages/app-bridge-theme/src/types/Command.ts b/packages/app-bridge-theme/src/types/Command.ts new file mode 100644 index 000000000..9f9bcf78d --- /dev/null +++ b/packages/app-bridge-theme/src/types/Command.ts @@ -0,0 +1,17 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type CommandRegistry } from '../registries'; + +import { type WrongNamePattern } from './Common'; + +type DispatchHandler< + CommandName extends keyof CommandRegistry, + TCommand extends CommandRegistry, +> = TCommand[CommandName] extends void ? { name: CommandName } : { name: CommandName; payload: TCommand[CommandName] }; + +export type DispatchHandlerParameter< + CommandName, + TCommand extends CommandRegistry, +> = CommandName extends keyof CommandRegistry + ? DispatchHandler + : WrongNamePattern; diff --git a/packages/app-bridge-theme/src/types/Common.ts b/packages/app-bridge-theme/src/types/Common.ts new file mode 100644 index 000000000..69da2781b --- /dev/null +++ b/packages/app-bridge-theme/src/types/Common.ts @@ -0,0 +1,17 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +type NameContextList = 'Command' | 'Event'; +export type WrongNamePattern = ApiMethodName extends string + ? `The following ${NameContext} do not match the naming pattern: ${ApiMethodName}` + : never; + +export type ObjectNameValidator< + NameObject, + PatternObject, + NameContext extends NameContextList, +> = keyof NameObject extends keyof PatternObject + ? NameObject + : WrongNamePattern< + `${Exclude, Extract>}`, + NameContext + >; diff --git a/packages/app-bridge-theme/src/types/Context.ts b/packages/app-bridge-theme/src/types/Context.ts new file mode 100644 index 000000000..7d70c7f7d --- /dev/null +++ b/packages/app-bridge-theme/src/types/Context.ts @@ -0,0 +1,73 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type Asset } from './Asset'; +import { type EventUnsubscribeFunction } from './Event'; +import { + type PortalNavigationItem, + type BrandPortalLink, + type CoverPage, + type Document, + type DocumentLibrary, + type DocumentPage, + type DocumentChildNavigationItem, +} from './Guideline'; +import { type Language } from './Language'; +import { type ThemeTemplate } from './ThemeTemplate'; + +export type TemplateContext = { templateId: string; type: ThemeTemplate } & ( + | { type: 'documentPage'; document: Document; documentPage: DocumentPage } + | { type: 'library'; document: DocumentLibrary } + | { type: 'cover'; coverPage: CoverPage } +); + +export type Context = { + brandId: number; + brandPortalLink: BrandPortalLink | null; + projectId: number; + portalId: number; + portalNavigation: PortalNavigationItem[] | null; + documentNavigation: Record; + portalToken: string | null; + currentLanguage: string; + defaultLanguage: string; + enabledFeatures: string[]; + isEditing: boolean; + isPublicLink: boolean; + isAuthenticated: boolean; + isAiBrandAssistantDialogOpen: boolean; + isSearchDialogOpen: boolean; + isPlatformAppsDialogOpen: boolean; + activeSectionHeadingId: number | null; + languages: Language[]; + template: TemplateContext | null; + settings: { + templateSettings: Record; + templateAssets: Record; + }; +}; + +export type ContextReturn = Key extends keyof Context + ? { + /** + * Gets the current value of the context object at the given key. + */ + get(): Readonly; + /** + * Subscribes to changes in the context object at the given key. + */ + subscribe( + callbackFunction: (nextContext: Context[Key], previousContext: Context[Key]) => void, + ): EventUnsubscribeFunction; + } + : { + /** + * Gets the current value of the context object. + */ + get(): Readonly; + /** + * Subscribes to changes in the context object. + */ + subscribe( + callbackFunction: (nextContext: Context, previousContext: Context) => void, + ): EventUnsubscribeFunction; + }; diff --git a/packages/app-bridge-theme/src/types/Event.ts b/packages/app-bridge-theme/src/types/Event.ts new file mode 100644 index 000000000..917b74828 --- /dev/null +++ b/packages/app-bridge-theme/src/types/Event.ts @@ -0,0 +1,64 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type Simplify } from 'type-fest'; + +import { type EventRegistry } from '../registries'; + +import { type ObjectNameValidator, type WrongNamePattern } from './Common'; +import { type Context } from './Context'; +import { type State } from './State'; + +type EventVerb = 'chosen'; + +export type EventNameValidator = Simplify< + ObjectNameValidator +>; + +type ContextAsEventName = { + [ContextKey in keyof Context as ContextKey extends string ? `Context.${ContextKey}` : never]: [ + Context[ContextKey], + Context[ContextKey], + ]; +}; + +type StateAsEventName = { + [StateKey in keyof State as StateKey extends string ? `State.${StateKey}` : never]: [ + State[StateKey], + State[StateKey], + ]; +}; + +export type AppBridgeThemeEvent = EventNameValidator< + Pick & + ContextAsEventName< + Context & { + '*': Context; + } + > & + StateAsEventName< + State & { + '*': State; + } + > +>; + +export type EventNamePattern = { + [eventName: `Context.${string}` | `State.${string}` | `${string}${Capitalize}`]: unknown; +}; +export type EventNameParameter< + EventName, + EventNameParameter extends EventNamePattern, +> = EventName extends keyof EventNameParameter ? EventName : WrongNamePattern; + +export type EventCallbackParameter = EventName extends keyof AppBridgeThemeEvent + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + AppBridgeThemeEvent[EventName] extends any[] + ? (...eventReturn: AppBridgeThemeEvent[EventName]) => void + : (eventReturn: AppBridgeThemeEvent[EventName]) => void + : () => void; + +export type EventUnsubscribeFunction = () => void; + +export type SubscribeMap = { + [EventName in keyof Event as EventName]: Map, boolean>; +}; diff --git a/packages/app-bridge-theme/src/types/Guideline.ts b/packages/app-bridge-theme/src/types/Guideline.ts new file mode 100644 index 000000000..1ea5dad84 --- /dev/null +++ b/packages/app-bridge-theme/src/types/Guideline.ts @@ -0,0 +1,126 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export enum LinkSettingsDisplay { + TextAndIcon = 'ICON_TEXT', + IconOnly = 'ICON', + TextOnly = 'TEXT', +} + +export enum LinkSettingsIconPosition { + Right = 'RIGHT', + Left = 'LEFT', +} + +interface CoverPageBase { + id(): number; + title(language?: string): string; + isPublished(): boolean; + isHiddenInNavigation(): boolean; + url(language?: string): string; + type: 'cover-page'; +} +export interface CoverPageNavigationItem extends CoverPageBase {} +export interface CoverPage extends CoverPageBase {} + +export interface DocumentGroupNavigationItem { + id(): number; + title(language?: string): string; + children(): (DocumentNavigationItem | DocumentLibraryNavigationItem | DocumentLinkNavigationItem)[]; + type: 'document-group'; +} + +interface DocumentBase { + id(): number; + title(language?: string): string; + slug(language?: string): string; + url(language?: string): string; + type: 'document'; +} +export interface DocumentNavigationItem extends DocumentBase { + parentId(): Nullable; +} +export interface Document extends DocumentBase { + documentGroupId(): Nullable; +} + +interface DocumentLibraryBase { + id(): number; + title(language?: string): string; + slug(language?: string): string; + url(language?: string): string; + type: 'document-library'; +} +export interface DocumentLibraryNavigationItem extends DocumentLibraryBase { + parentId(): Nullable; +} +export interface DocumentLibrary extends DocumentLibraryBase { + documentGroupId(): Nullable; +} + +export interface DocumentLinkNavigationItem { + id(): number; + title(language?: string): string; + url(): string; + displayMode(): LinkSettingsDisplay; + iconPosition(): LinkSettingsIconPosition; + customIconUrl(): Nullable; + shouldOpenInNewTab(): boolean; + parentId(): Nullable; + type: 'document-link'; +} + +export interface PageCategoryNavigationItem { + id(): number; + title(language?: string): string; + slug(language?: string): string; + children(): (DocumentPageNavigationItem | DocumentPageLinkNavigationItem)[]; + type: 'page-category'; +} + +interface DocumentPageBase { + id(): number; + isPublished(): boolean; + title(language?: string): string; + slug(language?: string): string; + url(language?: string): string; + type: 'document-page'; +} +export interface DocumentPageNavigationItem extends DocumentPageBase { + headings(): DocumentPageHeadingNavigationItem[]; +} +export interface DocumentPage extends DocumentPageBase { + categoryId(): Nullable; + documentId(): number; +} + +export interface DocumentPageLinkNavigationItem { + id(): number; + title(language?: string): string; + url(): string; + type: 'document-page-link'; +} + +export interface DocumentPageHeadingNavigationItem { + id(): number; + title(language?: string): string; + slug(language?: string): string; + type: 'document-page-heading'; +} + +export interface BrandPortalLink { + isEnabled(): boolean; + title(language?: string): string; + url(language?: string): string; +} + +export type PortalNavigationItem = + | CoverPageNavigationItem + | DocumentGroupNavigationItem + | DocumentNavigationItem + | DocumentLibraryNavigationItem + | DocumentLinkNavigationItem; + +export type DocumentChildNavigationItem = + | PageCategoryNavigationItem + | DocumentPageNavigationItem + | DocumentPageLinkNavigationItem; diff --git a/packages/app-bridge-theme/src/types/GuidelineSearchResult.ts b/packages/app-bridge-theme/src/types/GuidelineSearchResult.ts new file mode 100644 index 000000000..5bfd27ec1 --- /dev/null +++ b/packages/app-bridge-theme/src/types/GuidelineSearchResult.ts @@ -0,0 +1,31 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export const GuidelineSearchResultTypeMap = { + block: 'BLOCK', + section: 'SECTION', + page: 'PAGE', + color: 'COLOR', +}; +type GuidelineSearchResultType = (typeof GuidelineSearchResultTypeMap)[keyof typeof GuidelineSearchResultTypeMap]; + +export type GuidelineSearchResult = { + highlights: string[]; + type: GuidelineSearchResultType; + objectId: number; + pageId: number; + pageSlug: string; + pageTitle: string; + pageCategorySlug: string | null; + blockId: number; + documentId: number; + documentSlug: string; + documentTitle: string; + portalId: number; + portalToken: string | null; + sectionId: string | null; + sectionSlug: string | null; + sectionTitle: string | null; + colorHex?: string; + projectColorId?: string; + guidelineTitle: string; +}; diff --git a/packages/app-bridge/src/types/Language.ts b/packages/app-bridge-theme/src/types/Language.ts similarity index 100% rename from packages/app-bridge/src/types/Language.ts rename to packages/app-bridge-theme/src/types/Language.ts diff --git a/packages/app-bridge-theme/src/types/State.ts b/packages/app-bridge-theme/src/types/State.ts new file mode 100644 index 000000000..335a6687d --- /dev/null +++ b/packages/app-bridge-theme/src/types/State.ts @@ -0,0 +1,42 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type EventUnsubscribeFunction } from './Event.ts'; + +export type State = { + /** + * The total height of any sticky elements at the top of the document in pixels. + */ + scrollTopOffset: number; +}; + +export type StateReturn = Key extends keyof State + ? { + /** + * Gets the current value of the state object at the given key. + */ + get(): Readonly; + /** + * Sets a new value of the state object at the given key. + */ + set(nextState: State[Key]): void; + /** + * Subscribes to changes in the state object at the given key. + */ + subscribe( + callbackFunction: (nextState: State[Key], previousState: State[Key]) => void, + ): EventUnsubscribeFunction; + } + : { + /** + * Gets the current value of the state object. + */ + get(): Readonly; + /** + * Sets a new value of the state object. + */ + set(nextState: State): void; + /** + * Subscribes to changes in the state object. + */ + subscribe(callbackFunction: (nextState: State, previousState: State) => void): EventUnsubscribeFunction; + }; diff --git a/packages/app-bridge/src/types/ThemeTemplate.ts b/packages/app-bridge-theme/src/types/ThemeTemplate.ts similarity index 100% rename from packages/app-bridge/src/types/ThemeTemplate.ts rename to packages/app-bridge-theme/src/types/ThemeTemplate.ts diff --git a/packages/app-bridge-theme/src/types/index.ts b/packages/app-bridge-theme/src/types/index.ts new file mode 100644 index 000000000..1860dce5e --- /dev/null +++ b/packages/app-bridge-theme/src/types/index.ts @@ -0,0 +1,12 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './Asset'; +export * from './Command'; +export * from './Common'; +export * from './Context'; +export * from './Event'; +export * from './Guideline'; +export * from './GuidelineSearchResult'; +export * from './Language'; +export * from './State'; +export * from './ThemeTemplate'; diff --git a/packages/app-bridge-theme/tsconfig.json b/packages/app-bridge-theme/tsconfig.json new file mode 100644 index 000000000..2b70f17a7 --- /dev/null +++ b/packages/app-bridge-theme/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2021", + "useDefineForClassFields": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "types": ["vite/client"] + }, + "include": ["src", "./tests/setupTests.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/app-bridge-theme/tsconfig.node.json b/packages/app-bridge-theme/tsconfig.node.json new file mode 100644 index 000000000..dcb1d4ec2 --- /dev/null +++ b/packages/app-bridge-theme/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "lib": ["ESNext"], + "target": "ESNext" + }, + "include": [".eslintrc.cjs", ".prettierrc.cjs", "vite.config.mts", "package.json"] +} diff --git a/packages/app-bridge-theme/vite.config.mts b/packages/app-bridge-theme/vite.config.mts new file mode 100644 index 000000000..4266c9261 --- /dev/null +++ b/packages/app-bridge-theme/vite.config.mts @@ -0,0 +1,42 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; + +import { dependencies as dependenciesMap, peerDependencies as peerDependenciesMap } from './package.json'; + +const dependencies = Object.keys(dependenciesMap); +const peerDependencies = Object.keys(peerDependenciesMap); + +export const globals = { + react: 'React', + 'react-dom': 'ReactDOM', +}; + +export default defineConfig({ + plugins: [dts({ insertTypesEntry: true, rollupTypes: true })], + build: { + lib: { + entry: { + index: './src/index.ts', + }, + name: 'app-bridge-theme', + }, + sourcemap: true, + minify: true, + rollupOptions: { + external: [...dependencies, ...peerDependencies], + }, + }, + test: { + environment: 'happy-dom', + css: true, + coverage: { + all: true, + reporter: ['text', 'lcov'], + include: ['src/**/*.ts', 'src/**/*.tsx'], + exclude: ['src/**/test.ts', 'src/**/test.tsx', 'src/**/spec.ts', 'src/**/spec.tsx'], + }, + setupFiles: ['./src/tests/setupTests.ts'], + }, +}); diff --git a/packages/app-bridge/CHANGELOG.md b/packages/app-bridge/CHANGELOG.md index ba6f1df46..f5f66b15e 100644 --- a/packages/app-bridge/CHANGELOG.md +++ b/packages/app-bridge/CHANGELOG.md @@ -1,5 +1,41 @@ # @frontify/app-bridge +## 4.0.0-alpha.42 + +### Patch Changes + +- [#1237](https://github.com/Frontify/brand-sdk/pull/1237) [`eb6822d`](https://github.com/Frontify/brand-sdk/commit/eb6822d507e6a96a77725cb4aeb16d26846b72ee) Thanks [@anxobotana](https://github.com/anxobotana)! - refactor: Removed `appBridgeTheme` from the `@frontify/app-bridge` package. It is now provided via the `@frontify/app-bridge-theme` package. + +## 4.0.0-alpha.41 + +### Patch Changes + +- [#1235](https://github.com/Frontify/brand-sdk/pull/1235) [`02fd19d`](https://github.com/Frontify/brand-sdk/commit/02fd19d27220470f6ba523a0cae29fc36ab55758) Thanks [@ragi96](https://github.com/ragi96)! - fix: AssetChooserResult Type as alternative text is prefixed with computed + +## 4.0.0-alpha.40 + +### Minor Changes + +- [#1232](https://github.com/Frontify/brand-sdk/pull/1232) [`559f24e`](https://github.com/Frontify/brand-sdk/commit/559f24efe287c913ecfc0755ff18c56fcfe2fa56) Thanks [@ragi96](https://github.com/ragi96)! - feat(Asset): added the property alternativeText + +## 4.0.0-alpha.39 + +### Patch Changes + +- [#1206](https://github.com/Frontify/brand-sdk/pull/1206) [`7abe2d7`](https://github.com/Frontify/brand-sdk/commit/7abe2d7635dd7f319497fd4e6e422873d7075489) Thanks [@ragi96](https://github.com/ragi96)! - refactor: remove AppBridgeCreateAsset, please use `@frontify/app-bridge-app` + +## 4.0.0-alpha.38 + +### Patch Changes + +- [#864](https://github.com/Frontify/brand-sdk/pull/864) [`cd6ac0b`](https://github.com/Frontify/brand-sdk/commit/cd6ac0b022bca6a3ca663be801f0dbe62d129728) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - chore: merge main into dev + +## 4.0.0-alpha.37 + +### Patch Changes + +- chore: merge main to dev + ## 3.11.1 ### Patch Changes @@ -8,12 +44,132 @@ - [#1158](https://github.com/Frontify/brand-sdk/pull/1158) [`8937b15`](https://github.com/Frontify/brand-sdk/commit/8937b15b6dfcf17c288f4b22643c991ae74d55a4) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - fix: do not hardcode upload chunk sizes -## 3.11.0 +## 4.0.0-alpha.36 + +### Patch Changes + +- [#1080](https://github.com/Frontify/brand-sdk/pull/1080) [`a78218b`](https://github.com/Frontify/brand-sdk/commit/a78218b302929a05616f2d157968aa007cf36115) Thanks [@julianiff](https://github.com/julianiff)! - Fix: readd method + +## 4.0.0-alpha.35 + +### Patch Changes + +- [#1078](https://github.com/Frontify/brand-sdk/pull/1078) [`78555ee`](https://github.com/Frontify/brand-sdk/commit/78555ee76fbb487cf4a7c0fe3cda419704b35f63) Thanks [@julianiff](https://github.com/julianiff)! - chore: remove appBridgePlatformApp + +## 4.0.0-alpha.34 + +### Patch Changes + +- [#1042](https://github.com/Frontify/brand-sdk/pull/1042) [`e345d27`](https://github.com/Frontify/brand-sdk/commit/e345d2747d00efeadd8130dcde7f0dd1f2c398bf) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: add documentId to GuidelineDocumentCategory events + +## 4.0.0-alpha.33 + +### Patch Changes + +- chore: merge main to dev + +## 4.0.0-alpha.32 + +### Patch Changes + +- [#1007](https://github.com/Frontify/brand-sdk/pull/1007) [`2c0ebf2`](https://github.com/Frontify/brand-sdk/commit/2c0ebf209915a11dd018b7453d7932d0f0ee6f3e) Thanks [@ragi96](https://github.com/ragi96)! - chore: merge main to dev + +- [#992](https://github.com/Frontify/brand-sdk/pull/992) [`f60bbf4`](https://github.com/Frontify/brand-sdk/commit/f60bbf42218880ee8042e5830152a2112eef09dc) Thanks [@julianiff](https://github.com/julianiff)! - feat: deprecate react hooks for platform apps + +## 4.0.0-alpha.31 + +### Patch Changes + +- [#956](https://github.com/Frontify/brand-sdk/pull/956) [`2491b6e`](https://github.com/Frontify/brand-sdk/commit/2491b6e88fc41ca8eafff5e41ec181247a968c46) Thanks [@julianiff](https://github.com/julianiff)! - feat: adds state listener to allow push from parent + +## 4.0.0-alpha.30 + +### Patch Changes + +- chore: merge branch `main` into dev + +## 4.0.0-alpha.29 + +### Patch Changes + +- [#941](https://github.com/Frontify/brand-sdk/pull/941) [`4e643ec`](https://github.com/Frontify/brand-sdk/commit/4e643ec9414e5671f877c556b68436a1a06569cb) Thanks [@julianiff](https://github.com/julianiff)! - Feat: adjust secure request response + +## 4.0.0-alpha.28 + +### Patch Changes + +- [#939](https://github.com/Frontify/brand-sdk/pull/939) [`7ea653c`](https://github.com/Frontify/brand-sdk/commit/7ea653ca17f2a16c72e8ee5525580eaee0d6405a) Thanks [@julianiff](https://github.com/julianiff)! - refactor: rename brandsdk name from secretRequest to secureRequest + +## 4.0.0-alpha.27 + +### Patch Changes + +- [#937](https://github.com/Frontify/brand-sdk/pull/937) [`341886e`](https://github.com/Frontify/brand-sdk/commit/341886e9d16184048f9285d692df5177bfd2d0d1) Thanks [@julianiff](https://github.com/julianiff)! - chore: rename brand sdk name + +## 4.0.0-alpha.26 + +### Patch Changes + +- [#929](https://github.com/Frontify/brand-sdk/pull/929) [`414ea40`](https://github.com/Frontify/brand-sdk/commit/414ea4092d5446815df032a87471829801604634) Thanks [@julianiff](https://github.com/julianiff)! - refactor: Method to reflect feature + +## 4.0.0-alpha.25 + +### Patch Changes + +- chore: merge main to dev + +## 4.0.0-alpha.24 + +### Patch Changes + +- chore: merge main to dev + +## 4.0.0-alpha.23 + +### Patch Changes + +- [#918](https://github.com/Frontify/brand-sdk/pull/918) [`1d48008`](https://github.com/Frontify/brand-sdk/commit/1d48008561a2dc3f4b7b4c9fcb7bc44cd7d04bc6) Thanks [@SamCreasey](https://github.com/SamCreasey)! - chore: Merge main into dev + +## 4.0.0-alpha.22 + +### Patch Changes + +- chore: merge branch `main` into dev + +## 4.0.0-alpha.21 + +### Patch Changes + +- [#910](https://github.com/Frontify/brand-sdk/pull/910) [`0723d89`](https://github.com/Frontify/brand-sdk/commit/0723d89575f47682bc146661e693651f61965c8c) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: (AppBridgeTheme) update appBridgeThemeStub with context and state + +## 4.0.0-alpha.20 + +### Patch Changes + +- [#900](https://github.com/Frontify/brand-sdk/pull/900) [`9294b16`](https://github.com/Frontify/brand-sdk/commit/9294b164ad2b3fc502004e3d8d86ee29737ac822) Thanks [@julianiff](https://github.com/julianiff)! - feat(AppBridgePlatformApp): only one app bridge per iframe + +- [#864](https://github.com/Frontify/brand-sdk/pull/864) [`2a5b9cf`](https://github.com/Frontify/brand-sdk/commit/2a5b9cfb1e73331a3a4d39550a3a26de8a7e0c94) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - feat: merge `main` into `dev` + +- [#864](https://github.com/Frontify/brand-sdk/pull/864) [`2a5b9cf`](https://github.com/Frontify/brand-sdk/commit/2a5b9cfb1e73331a3a4d39550a3a26de8a7e0c94) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - feat(AppBridgeTheme): add isSearchDialogOpen to context + +## 4.0.0-alpha.19 + +### Patch Changes + +- [#895](https://github.com/Frontify/brand-sdk/pull/895) [`b31a73d`](https://github.com/Frontify/brand-sdk/commit/b31a73d1756f01374682552a3013173f3c8f5384) Thanks [@julianiff](https://github.com/julianiff)! - feat: add user state variable + +## 4.0.0-alpha.18 + +### Patch Changes + +- [#884](https://github.com/Frontify/brand-sdk/pull/884) [`4fe96c8`](https://github.com/Frontify/brand-sdk/commit/4fe96c8808d1b85541dd6cc5ed2711bc5a47f4eb) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - fix(useDocumentNavigation): Fixed an issue that prevented debounced callbacks not to be executed + fix(usePortalNavigation): Fixed an issue that prevented debounced callbacks not to be executed + +## 4.0.0-alpha.17 ### Minor Changes -- [#1136](https://github.com/Frontify/brand-sdk/pull/1136) [`6bafe6c`](https://github.com/Frontify/brand-sdk/commit/6bafe6c0aadc0dcba8a8b3c7bb283e219c47b5ef) Thanks [@ragi96](https://github.com/ragi96)! - refactor(AssetChooserType): remove PatternLibrary as it is sunset - refactor(DocumentMode): remove PatternLibrary as it is sunset +- # [#864](https://github.com/Frontify/brand-sdk/pull/864) [`c46694a`](https://github.com/Frontify/brand-sdk/commit/c46694abe2ce06d78e8bd27ef832e92244116108) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - feat(AppBridgeBlock): adds `creationFormUri` to the type `template` ## 3.10.1 @@ -104,18 +260,42 @@ - [#871](https://github.com/Frontify/brand-sdk/pull/871) [`cc7532a`](https://github.com/Frontify/brand-sdk/commit/cc7532ae5a3803f12997887e9f1ca9993e9f8e7c) Thanks [@ryancarville](https://github.com/ryancarville)! - feat(AppBridgeBlock): adds `creationFormUri` to the type `template` +## 4.0.0-alpha.16 + +### Patch Changes + +- [#877](https://github.com/Frontify/brand-sdk/pull/877) [`1dd80ad`](https://github.com/Frontify/brand-sdk/commit/1dd80ad39568e96e97ab73c04feee3a949764ae9) Thanks [@mike85](https://github.com/mike85)! - feat(usePageTemplateSettings): returns also templateThemeSettings + ## 3.5.9 ### Patch Changes - [#875](https://github.com/Frontify/brand-sdk/pull/875) [`d74dc90`](https://github.com/Frontify/brand-sdk/commit/d74dc90501cd0aac39f67a834014fd9c261abfb0) Thanks [@anxobotana](https://github.com/anxobotana)! - feat(usePageTemplateSettings): returns also templateThemeSettings +## 4.0.0-alpha.15 + +### Patch Changes + +- [#864](https://github.com/Frontify/brand-sdk/pull/864) [`0f17eb8`](https://github.com/Frontify/brand-sdk/commit/0f17eb82afdc30692e02d1cf150d622f38d0913a) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - feat: merge `main` into `dev` + ## 3.5.8 ### Patch Changes - [#868](https://github.com/Frontify/brand-sdk/pull/868) [`88fb3c3`](https://github.com/Frontify/brand-sdk/commit/88fb3c3b0235d9fc89c9a352273bacfaf53fdb0e) Thanks [@mike85](https://github.com/mike85)! - feat(AppBridgeTheme): add useDocumentNavigation hook +## 4.0.0-alpha.14 + +### Major Changes + +- [#841](https://github.com/Frontify/brand-sdk/pull/841) [`d88b50d`](https://github.com/Frontify/brand-sdk/commit/d88b50dd6e2235262dbae7250db053f9052228d1) Thanks [@SamCreasey](https://github.com/SamCreasey)! - feat: modify `DocumentSection` and `DocumentSectionApi` type to allow `title` to be `null`. + +### Patch Changes + +- [#841](https://github.com/Frontify/brand-sdk/pull/841) [`d88b50d`](https://github.com/Frontify/brand-sdk/commit/d88b50dd6e2235262dbae7250db053f9052228d1) Thanks [@SamCreasey](https://github.com/SamCreasey)! - - feat(useDocumentSection): Subscribe hook to emitter event listeners. A new emitter type, `AppBridge:GuidelineDocumentSection:Action` has been added. This emitter can be used to add and remove items from the sections saved in the hook state. + + - feat(useDocumentSection): `navigationItems` is now returned from this hook. This array filters out sections with an unreadable title and should be used to create section navigation links. + ## 3.5.7 ### Patch Changes @@ -130,12 +310,42 @@ - [#855](https://github.com/Frontify/brand-sdk/pull/855) [`bcfe9ab`](https://github.com/Frontify/brand-sdk/commit/bcfe9abea8872b8341b053159827a953bdbea16c) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - chore: bump dependencies -## 3.5.5 +## 4.0.0-alpha.13 ### Patch Changes - [#843](https://github.com/Frontify/brand-sdk/pull/843) [`3109e66`](https://github.com/Frontify/brand-sdk/commit/3109e66248fb9185c312d8170055452dce796bc4) Thanks [@julianiff](https://github.com/julianiff)! - chore: move up to v4 +- [`0d9d0af`](https://github.com/Frontify/brand-sdk/commit/0d9d0afe4fc55b0e0c5b1e397aeaf77e7e8c4a69) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - Merge main into dev + +## 4.0.0-alpha.12 + +### Patch Changes + +- [`18c91b1`](https://github.com/Frontify/brand-sdk/commit/18c91b184651f49c490d381054b3ec543f928551) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - Merge `main` into `dev` + +## 4.0.0-alpha.11 + +### Minor Changes + +- [#830](https://github.com/Frontify/brand-sdk/pull/830) [`e14ef89`](https://github.com/Frontify/brand-sdk/commit/e14ef895606e72af066e614983b61ca8778c108d) Thanks [@ragi96](https://github.com/ragi96)! - feat(AppBridgeBlock): add `isAuthenticated` to `BlockContext` type + +## 4.0.0-alpha.10 + +### Minor Changes + +- [#808](https://github.com/Frontify/brand-sdk/pull/808) [`4529beb`](https://github.com/Frontify/brand-sdk/commit/4529beb4b9f20ba94c7d6226257b422d6b560d5a) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: add navigateToDocumentSection command to AppBridgeTheme + +### Patch Changes + +- [#810](https://github.com/Frontify/brand-sdk/pull/810) [`0ac69ea`](https://github.com/Frontify/brand-sdk/commit/0ac69eaa52ba58a5be06ce75afa63cf5838e958b) Thanks [@anxobotana](https://github.com/anxobotana)! - fix: useGroupedDocuments and useUngroupedDocuments flaky tests + +## 4.0.0-alpha.9 + +### Patch Changes + +- Merge main into dev + ## 3.5.4 ### Patch Changes @@ -186,6 +396,14 @@ - [`babce0e`](https://github.com/Frontify/brand-sdk/commit/babce0eb251fa78ef9c7b4c2c0ce740c7d66718d) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - Add the dist folder to published packages +## 4.0.0-alpha.8 + +### Patch Changes + +- [#777](https://github.com/Frontify/brand-sdk/pull/777) [`b5d1cb8`](https://github.com/Frontify/brand-sdk/commit/b5d1cb8a376ccada20d0234913584a6bdba0cba9) Thanks [@julianiff](https://github.com/julianiff)! - Fix: add retry to subscription + +- Merge main into dev + ## 3.4.1 ### Patch Changes @@ -200,18 +418,65 @@ - [#775](https://github.com/Frontify/brand-sdk/pull/775) [`3837782`](https://github.com/Frontify/brand-sdk/commit/383778258e862fb4fef62bf33aa71db71727aca1) Thanks [@mike85](https://github.com/mike85)! - feat: add open/close search dialog commands to AppBridgeTheme +## 4.0.0-alpha.7 + +### Minor Changes + +- [#780](https://github.com/Frontify/brand-sdk/pull/780) [`40b18c4`](https://github.com/Frontify/brand-sdk/commit/40b18c473478750ce04eb18a7ff290e34b65b94f) Thanks [@mike85](https://github.com/mike85)! - feat: add open/close search dialog commands to AppBridgeTheme + +## 4.0.0-alpha.6 + +### Patch Changes + +- [#772](https://github.com/Frontify/brand-sdk/pull/772) [`3e44c59`](https://github.com/Frontify/brand-sdk/commit/3e44c596628abcad11414eb26a954f4cb6eea097) Thanks [@julianiff](https://github.com/julianiff)! - feat: switch to platformAppBridge to v4 + +- [#774](https://github.com/Frontify/brand-sdk/pull/774) [`7a80244`](https://github.com/Frontify/brand-sdk/commit/7a80244b2ebf075f93a492513970b3ee2c92dc80) Thanks [@julianiff](https://github.com/julianiff)! - feat: Adjust PlatformAppContext + refactor: rename from type to surface + +## 4.0.0-alpha.5 + +### Patch Changes + +- [`a4822db`](https://github.com/Frontify/brand-sdk/commit/a4822db8bb86c2516241395963979369e0311a7a) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - fix: document page duplication return type + +- [`5e4178c`](https://github.com/Frontify/brand-sdk/commit/5e4178c1e1028cec2af73bd34fedf56078f8b42c) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - Merge main into dev + ## 3.3.2 ### Patch Changes - [#769](https://github.com/Frontify/brand-sdk/pull/769) [`dde3283`](https://github.com/Frontify/brand-sdk/commit/dde328355d240a730ba6a6d465166c0ecc773548) Thanks [@oliverschwendener](https://github.com/oliverschwendener)! - fix: document page duplication return type +## 4.0.0-alpha.4 + +### Patch Changes + +- Merge main into dev + ## 3.3.1 ### Patch Changes - [#766](https://github.com/Frontify/brand-sdk/pull/766) [`3775b46`](https://github.com/Frontify/brand-sdk/commit/3775b4651b6d700e7179a360190a92e7f0fe51e6) Thanks [@SamuelAlev](https://github.com/SamuelAlev)! - Update dependencies +## 4.0.0-alpha.3 + +### Patch Changes + +- [#734](https://github.com/Frontify/brand-sdk/pull/734) [`029dd26`](https://github.com/Frontify/brand-sdk/commit/029dd26b26734539e80af86bcaa49d2caf9a7771) Thanks [@ragi96](https://github.com/ragi96)! - feat: adjust the color type to have a revision + +## 4.0.0-alpha.2 + +### Patch Changes + +- [#753](https://github.com/Frontify/brand-sdk/pull/753) [`50937f2`](https://github.com/Frontify/brand-sdk/commit/50937f2170f5ee1eab05726e141fc0e932f71865) Thanks [@ragi96](https://github.com/ragi96)! - feat: adds `setAssetIdsByBlockAssetKey` to AppBridgeBlock + +## 4.0.0-alpha.1 + +### Patch Changes + +- [#742](https://github.com/Frontify/brand-sdk/pull/742) [`5ea66a7`](https://github.com/Frontify/brand-sdk/commit/5ea66a79bac131f5b34f1a1bf60fbe81add6b17f) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: add context.template to AppBridgeTheme + ## 3.3.0 ### Minor Changes @@ -224,6 +489,12 @@ - [#731](https://github.com/Frontify/brand-sdk/pull/731) [`b9c4fc4`](https://github.com/Frontify/brand-sdk/commit/b9c4fc4fddacabb988ceb0cbe125c1793a697e19) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: add theme template context to appBridgeThemes context +## 4.0.0-alpha.0 + +### Major Changes + +- [#705](https://github.com/Frontify/brand-sdk/pull/705) [`a027b87`](https://github.com/Frontify/brand-sdk/commit/a027b87593ae25d18cda62c4989920a16958d28c) Thanks [@ragi96](https://github.com/ragi96)! - feat: first alpha release, removes all deprecated functions on the AppBridgeBlock + ## 3.1.0 ### Minor Changes diff --git a/packages/app-bridge/package.json b/packages/app-bridge/package.json index 8514449b0..dbd5da2a0 100644 --- a/packages/app-bridge/package.json +++ b/packages/app-bridge/package.json @@ -1,7 +1,7 @@ { "name": "@frontify/app-bridge", "type": "module", - "version": "3.11.1", + "version": "4.0.0-alpha.42", "description": "Package to establish communication between Frontify and marketplace apps", "author": "Frontify Developers ", "repository": { diff --git a/packages/app-bridge/src/AppBridge.ts b/packages/app-bridge/src/AppBridge.ts index 2082d0ddf..f58b922cd 100644 --- a/packages/app-bridge/src/AppBridge.ts +++ b/packages/app-bridge/src/AppBridge.ts @@ -182,7 +182,7 @@ export interface AppBridge< * Sends a command to the Frontify platform. * * @returns A promise that resolves to acknowledge the dispatch. - * The event will be triggered at a later stage and can be subscribed to with {@link AppBridgePlatformApp.subscribe}. + * The event will be triggered at a later stage and can be subscribed to */ dispatch( dispatchHandler: DispatchHandlerParameter, @@ -217,7 +217,6 @@ export interface AppBridge< /** * Subscribes to an event with the given name and callback function. - * The event are fired from {@link AppBridgePlatformApp.dispatch}, {@link AppBridgePlatformApp.state} and {@link AppBridgePlatformApp.context}. * * @returns Function that can be used to unsubscribe from an event. * When called, this function will remove the subscription to the event and prevent any further callbacks from being executed. diff --git a/packages/app-bridge/src/AppBridgeBlock.ts b/packages/app-bridge/src/AppBridgeBlock.ts index 998a35e2c..8f0bca74c 100644 --- a/packages/app-bridge/src/AppBridgeBlock.ts +++ b/packages/app-bridge/src/AppBridgeBlock.ts @@ -21,7 +21,6 @@ import { type CommandRegistry } from './registries/commands/CommandRegistry'; import { type EventRegistry } from './registries/events/EventRegistry'; import { type Asset, - type AssetChooserOptions, type BulkDownload, type Color, type ColorCreate, @@ -105,18 +104,6 @@ export interface AppBridgeBlock< callback: EventCallbackParameter, ): EventUnsubscribeFunction; - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('blockId').get()` instead. - */ - getBlockId(): number; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('sectionId').get()` instead. - */ - getSectionId(): number | undefined; - getBlockAssets(): Promise>; getAssetById(assetId: number): Promise; @@ -125,12 +112,6 @@ export interface AppBridgeBlock< addAssetIdsToBlockAssetKey(key: string, assetIds: number[]): Promise; - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(openAssetViewer(token))` instead. - */ - openAssetViewer(token: string): void; - getBlockTemplates(): Promise>; addTemplateIdsToBlockTemplateKey(key: string, templateIds: number[]): Promise>; @@ -143,16 +124,6 @@ export interface AppBridgeBlock< getColors(): Promise; - /** - * @deprecated Use `getColors` instead. - */ - getAvailableColors(): Promise; - - /** - * @deprecated Use `getColorPalettes` instead. - */ - getAvailablePalettes(): Promise; - getColorPalettesWithColors(colorPaletteIds?: number[]): Promise; createColorPalette(colorPaletteCreate: ColorPaletteCreate): Promise; @@ -173,18 +144,6 @@ export interface AppBridgeBlock< updateBlockSettings>(newSettings: T): Promise; - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(openTemplateChooser())` instead. - */ - openTemplateChooser(callback: (selectedTemplate: TemplateLegacy) => void): void; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(closeTemplateChooser())` instead. - */ - closeTemplateChooser(): void; - getCurrentLoggedUser(): Promise; getBulkDownloadByToken(token: string): Promise; @@ -193,19 +152,6 @@ export interface AppBridgeBlock< getPrivacySettings(): PrivacySettings; - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(openAssetChooser(options))` to open the asset chooser - * and `appBridge.subscribe('assetsChosen', callback)` to subscribe to the asset chosen event - */ - openAssetChooser(callback: (selectedAssets: Asset[]) => void, options?: AssetChooserOptions): void; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(closeAssetChooser())` instead. - */ - closeAssetChooser(): void; - getProjectId(): number; getEditorState(): boolean; diff --git a/packages/app-bridge/src/AppBridgeCreateAsset.spec.ts b/packages/app-bridge/src/AppBridgeCreateAsset.spec.ts deleted file mode 100644 index adf22ad17..000000000 --- a/packages/app-bridge/src/AppBridgeCreateAsset.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { afterEach, beforeAll, describe, expect, it, test, vi } from 'vitest'; - -import { AppBridgeCreateAsset } from './AppBridgeCreateAsset'; -import { Topic } from './types'; -import { generateRandomString } from './utilities/hash'; -import { notify } from './utilities/notify'; -import { subscribe } from './utilities/subscribe'; - -const TOKEN = 'AjY34F87Dsat^J'; -const EXPECTED_RESULT = { test: 'passed' }; - -const DEFAULT_TIMEOUT = 3 * 1000; -const LONG_TIMEOUT = 5 * 60 * 1000; - -describe('AppBridgeCreateAssetTest', () => { - beforeAll(() => { - vi.mock('./utilities/subscribe', () => { - return { - subscribe: vi.fn().mockResolvedValue({ test: 'passed' }), - }; - }); - - vi.mock('./utilities/hash', () => { - return { - generateRandomString: vi.fn().mockReturnValue('AjY34F87Dsat^J'), - }; - }); - - vi.mock('./utilities/notify', () => { - return { - notify: vi.fn(), - }; - }); - - expect(generateRandomString).toHaveBeenCalledTimes(1); - expect(generateRandomString).toHaveReturnedWith('AjY34F87Dsat^J'); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should be instantiable', () => { - expect(new AppBridgeCreateAsset()).toBeInstanceOf(AppBridgeCreateAsset); - }); - - test('getAppState', () => { - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.getAppState(); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.GetAppState, TOKEN); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.GetAppState, TOKEN); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('putAppState', () => { - const newState = { new: 'state' }; - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.putAppState(newState); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.PutAppState, TOKEN, newState); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.PutAppState, TOKEN); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('deleteAppState', () => { - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.deleteAppState(); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.DeleteAppState, TOKEN); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.DeleteAppState, TOKEN); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('getAssetById', () => { - const assetId = 4076; - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.getAssetById(assetId); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.GetAssetById, TOKEN, { assetId }); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.GetAssetById, TOKEN); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('postExternalAssetWithPreview', () => { - const assets = [ - { - title: 'My external asset', - url: 'https://www.post-external-asset.test', - previewUrl: 'https://www.preview-url.test', - }, - { - title: 'My external asset', - url: 'https://www.post-external-asset.test', - previewUrl: 'https://www.preview-url.test', - }, - ]; - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.postExternalAssets(assets); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.PostExternalAssets, TOKEN, assets); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.PostExternalAssets, TOKEN, { timeout: LONG_TIMEOUT }); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('postExternalAssetWithoutPreview', () => { - const assets = [ - { - title: 'My external asset', - url: 'https://www.post-external-asset.test', - }, - { - title: 'My external asset', - url: 'https://www.post-external-asset.test', - }, - ]; - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.postExternalAssets(assets); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.PostExternalAssets, TOKEN, assets); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.PostExternalAssets, TOKEN, { timeout: DEFAULT_TIMEOUT }); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('getThirdPartyOauth2Tokens', async () => { - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.getThirdPartyOauth2Tokens(); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.GetThirdPartyOauth2Tokens, TOKEN); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.GetThirdPartyOauth2Tokens, TOKEN, { - timeout: LONG_TIMEOUT, - }); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('getRefreshedThirdpartyOauth2Tokens', () => { - const refreshToken = '8raSsn0nG5v4'; - const appBridge = new AppBridgeCreateAsset(); - const result = appBridge.getRefreshedThirdpartyOauth2Tokens(refreshToken); - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.GetRefreshedThirdpartyOauth2Token, TOKEN, { refreshToken }); - - expect(subscribe).toHaveBeenCalledTimes(1); - expect(subscribe).toHaveBeenCalledWith(Topic.GetRefreshedThirdpartyOauth2Token, TOKEN); - expect(result).resolves.toEqual(EXPECTED_RESULT); - }); - - test('closeApp', () => { - const appBridge = new AppBridgeCreateAsset(); - appBridge.closeApp(); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.CloseApp, TOKEN); - }); - - test('openAssetChooser', () => { - const appBridge = new AppBridgeCreateAsset(); - appBridge.openAssetChooser(); - - expect(notify).toHaveBeenCalledTimes(1); - expect(notify).toHaveBeenCalledWith(Topic.OpenAssetChooser, TOKEN); - }); -}); diff --git a/packages/app-bridge/src/AppBridgeCreateAsset.ts b/packages/app-bridge/src/AppBridgeCreateAsset.ts deleted file mode 100644 index b7528deac..000000000 --- a/packages/app-bridge/src/AppBridgeCreateAsset.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { Topic, type AssetApi, type NotifyData, type OauthTokens, type PostExternalAssetParams } from './types'; -import { generateRandomString, notify, subscribe } from './utilities'; - -const PUBSUB_TOKEN = generateRandomString(); -const DEFAULT_TIMEOUT = 3 * 1000; -const LONG_TIMEOUT = 5 * 60 * 1000; - -export class AppBridgeCreateAsset { - getAppState>(): Promise { - notify(Topic.GetAppState, PUBSUB_TOKEN); - return subscribe(Topic.GetAppState, PUBSUB_TOKEN); - } - - async putAppState(newState: NotifyData): Promise { - notify(Topic.PutAppState, PUBSUB_TOKEN, newState); - return subscribe(Topic.PutAppState, PUBSUB_TOKEN); - } - - async deleteAppState(): Promise { - notify(Topic.DeleteAppState, PUBSUB_TOKEN); - return subscribe(Topic.DeleteAppState, PUBSUB_TOKEN); - } - - getAssetById(assetId: number): Promise { - notify(Topic.GetAssetById, PUBSUB_TOKEN, { assetId }); - return subscribe(Topic.GetAssetById, PUBSUB_TOKEN); - } - - async postExternalAssets(assets: PostExternalAssetParams[]): Promise { - const assetsWithPreview = assets.filter((asset) => asset.previewUrl); - const timeout = assetsWithPreview.length > 0 ? LONG_TIMEOUT : DEFAULT_TIMEOUT; - - notify(Topic.PostExternalAssets, PUBSUB_TOKEN, assets); - return subscribe(Topic.PostExternalAssets, PUBSUB_TOKEN, { - timeout, - }); - } - - getThirdPartyOauth2Tokens(): Promise { - notify(Topic.GetThirdPartyOauth2Tokens, PUBSUB_TOKEN); - return subscribe(Topic.GetThirdPartyOauth2Tokens, PUBSUB_TOKEN, { - timeout: LONG_TIMEOUT, - }); - } - - getRefreshedThirdpartyOauth2Tokens(refreshToken: string): Promise { - notify(Topic.GetRefreshedThirdpartyOauth2Token, PUBSUB_TOKEN, { refreshToken }); - return subscribe(Topic.GetRefreshedThirdpartyOauth2Token, PUBSUB_TOKEN); - } - - closeApp(): void { - notify(Topic.CloseApp, PUBSUB_TOKEN); - } - - openAssetChooser(): void { - notify(Topic.OpenAssetChooser, PUBSUB_TOKEN); - } -} diff --git a/packages/app-bridge/src/AppBridgePlatformApp.spec.ts b/packages/app-bridge/src/AppBridgePlatformApp.spec.ts deleted file mode 100644 index de6e5cda4..000000000 --- a/packages/app-bridge/src/AppBridgePlatformApp.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { AppBridgePlatformApp } from './AppBridgePlatformApp'; -import { InitializationError } from './errors'; -import { openConnection } from './registries'; -import { notify } from './utilities'; - -const TOKEN = 'AjY34F87Dsat^J'; - -describe('AppBridgePlatformApp', () => { - vi.mock('./utilities/subscribe', () => ({ - subscribe: vi.fn().mockResolvedValue({ - statePort: { onmessage: vi.fn() }, - apiPort: { onmessage: vi.fn() }, - context: { parentId: 'parentId-test', connected: true }, - state: { settings: 'settings-test' }, - }), - })); - - vi.mock('./utilities/notify', () => ({ - notify: vi.fn(), - })); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should be instantiable', () => { - expect(new AppBridgePlatformApp()).toBeInstanceOf(AppBridgePlatformApp); - }); - - it('should not initialize without a token', async () => { - window.location.search = '?token='; - - const platformApp = new AppBridgePlatformApp(); - - await expect(() => platformApp.dispatch(openConnection())).rejects.toThrow(new InitializationError()); - expect(notify).toHaveBeenCalledTimes(0); - }); - - it('should yield true for Context.connected after dispatch', async () => { - const connected = true; - window.location.search = `?token=${TOKEN}`; - const platformApp = new AppBridgePlatformApp(); - platformApp.subscribe('Context.connected', () => { - expect(connected).toBe(true); - }); - platformApp.dispatch(openConnection()); - }); - - it.fails('should throw an error when api is not initialized', async () => { - window.location.search = `?token=${TOKEN}`; - - const platformApp = new AppBridgePlatformApp(); - await expect(() => platformApp.api({ name: 'getCurrentUser' })).rejects.toThrow(); - }); - - it('should return empty state when not inititalized', async () => { - const platformApp = new AppBridgePlatformApp(); - const state = platformApp.state().get(); - expect(state).toEqual({ settings: {} }); - }); - - it('should return state after app is initialized', async () => { - window.location.search = `?token=${TOKEN}`; - const platformApp = new AppBridgePlatformApp(); - platformApp.subscribe('Context.connected', (connected) => { - const state = platformApp.state().get(); - - expect(connected).toBe(true); - expect(state).toEqual({ settings: 'settings-test' }); - }); - platformApp.dispatch(openConnection()); - }); - - it('should yield true for Context.connected after dispatch', async () => { - window.location.search = `?token=${TOKEN}`; - const platformApp = new AppBridgePlatformApp(); - platformApp.subscribe('Context.connected', (connected) => { - const context = platformApp.context().get(); - const parentId = platformApp.context('parentId').get(); - - expect(connected).toBe(true); - expect(context).toEqual({ parentId: 'parentId-test', connected: true }); - expect(parentId).toEqual('parentId-test'); - }); - platformApp.dispatch(openConnection()); - }); - - it('should return correct object when subscribing to context', async () => { - window.location.search = `?token=${TOKEN}`; - const platformApp = new AppBridgePlatformApp(); - platformApp.context().subscribe((context) => { - expect({ parentId: 'parentId-test', connected: true }).toStrictEqual(context); - }); - platformApp.dispatch(openConnection()); - }); - - it('should return correct object when subscribing to context', async () => { - window.location.search = `?token=${TOKEN}`; - const platformApp = new AppBridgePlatformApp(); - platformApp.context().subscribe((context) => { - expect({ parentId: 'parentId-test', connected: true }).toStrictEqual(context); - platformApp.dispatch(openConnection()); - }); - platformApp.dispatch(openConnection()); - }); -}); diff --git a/packages/app-bridge/src/AppBridgePlatformApp.ts b/packages/app-bridge/src/AppBridgePlatformApp.ts deleted file mode 100644 index 4b66c9b3a..000000000 --- a/packages/app-bridge/src/AppBridgePlatformApp.ts +++ /dev/null @@ -1,275 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { - type ApiHandlerParameter, - type ApiMethodNameValidator, - type ApiReturn, - type AppBridge, - type CommandNameValidator, - type ContextAsEventName, - type ContextReturn, - type DispatchHandlerParameter, - type EventCallbackParameter, - type EventNameParameter, - type EventNameValidator, - type EventUnsubscribeFunction, - type StateAsEventName, - type StateReturn, - type SubscribeMap, -} from './AppBridge'; -import { InitializationError } from './errors'; -import { type ApiMethodRegistry, openConnection } from './registries'; -import { Topic } from './types'; -import { generateRandomString, notify, subscribe } from './utilities'; -import { ErrorMessageBus, type IMessageBus, MessageBus } from './utilities/MessageBus'; -import { getQueryParameters } from './utilities/queryParams'; - -export type PlatformAppApiMethod = ApiMethodNameValidator< - Pick ->; - -export type PlatformAppCommandRegistry = CommandNameValidator<{ - openConnection: void; -}>; - -export type PlatformAppCommand = CommandNameValidator>; - -export type PlatformAppState = { - settings: Record; -}; - -type InitializeEvent = { - apiPort: MessagePort; - statePort: MessagePort; - context: PlatformAppContext; - state: PlatformAppState; -}; - -type AppBaseProps = { - token: string; - marketplaceServiceAppId: string; - connected: boolean; - settings: { [key: string]: string }; - brandId: number; - domain: string; - parentId: string; -}; - -export type AssetActionContext = { - surface: 'assetAction'; - assetId: string; -} & AppBaseProps; - -export type AssetCreationContext = { - surface: 'assetCreation'; -} & AppBaseProps; - -export type PlatformAppContext = AssetActionContext | AssetCreationContext; - -export type PlatformAppEvent = EventNameValidator< - StateAsEventName & - ContextAsEventName ->; - -export class AppBridgePlatformApp implements IAppBridgePlatformApp { - private apiMessageBus: IMessageBus = new ErrorMessageBus(); - private stateMessageBus: IMessageBus = new ErrorMessageBus(); - private initialized: boolean = false; - private localContext?: PlatformAppContext; - private localState: PlatformAppState = { settings: {} }; - private maxRetries: number = 5; - - private readonly subscribeMap: SubscribeMap = { - 'State.*': new Map(), - 'State.settings': new Map(), - 'Context.*': new Map(), - 'Context.marketplaceServiceAppId': new Map(), - 'Context.token': new Map(), - 'Context.assetId': new Map(), - 'Context.brandId': new Map(), - 'Context.parentId': new Map(), - 'Context.domain': new Map(), - 'Context.surface': new Map(), - 'Context.connected': new Map(), - 'Context.settings': new Map(), - }; - - api( - apiHandler: ApiHandlerParameter, - ): ApiReturn { - return this.apiMessageBus.post({ - parameter: apiHandler, - }) as ApiReturn; - } - - private guardForInitialization() { - const initialContext = getQueryParameters(window.location.href); - if (!initialContext.token) { - throw new InitializationError(); - } - return this.initialized; - } - - async dispatch( - dispatchHandler: DispatchHandlerParameter, - ): Promise { - if (dispatchHandler.name === openConnection().name) { - if (this.guardForInitialization()) { - return; - } - - const checksum = generateRandomString(); - - notify(Topic.Init, checksum, { - token: getQueryParameters(window.location.href).token, - appBridgeVersion: 'v4', - }); - - await this.attemptSubscription(1, checksum); - } - } - - private async attemptSubscription(attempt: number, checksum: string): Promise { - try { - const { statePort, apiPort, context, state } = await subscribe(Topic.Init, checksum); - - this.apiMessageBus = new MessageBus(apiPort); - this.stateMessageBus = new MessageBus(statePort); - - this.localContext = context; - this.localState = state; - this.initialized = true; - - this.callSubscribedTopic('Context.connected', [true, false]); - this.callSubscribedTopic('Context.*', [this.localContext, this.localContext]); - this.callSubscribedTopic('State.*', [this.localState, this.localState]); - } catch (error) { - if (attempt < this.maxRetries) { - await this.attemptSubscription(attempt + 1, checksum); - } else { - console.error('Could not connect to the platform.'); - } - } - } - - context(key?: void): ContextReturn; - context(key?: Key): ContextReturn; - context(key?: keyof PlatformAppContext | void): unknown { - if (typeof key === 'undefined') { - return { - get: () => this.localContext, - subscribe: ( - callback: (nextContext: PlatformAppContext, previousContext: PlatformAppContext) => void, - ) => { - return this.subscribe('Context.*', callback); - }, - }; - } - return { - get: () => (this.localContext ? this.localContext[key] : {}), - subscribe: ( - callback: (nextContext: PlatformAppContext[Key], previousContext: PlatformAppContext[Key]) => void, - ) => { - // @ts-expect-error typing is correct, but TS doesn't like it - return this.subscribe(`Context.${key}`, callback); - }, - }; - } - - private async setInternalState(state: Promise): Promise { - const prevState = this.localState; - this.localState = await state; - this.callSubscribedTopic('State.*', [this.localState, prevState]); - } - - state(): StateReturn; - state(key: Key): StateReturn; - state(key?: keyof PlatformAppState | void): unknown { - if (typeof key === 'undefined') { - return { - get: () => this.localState, - set: (nextState: PlatformAppState) => { - const newState = this.stateMessageBus.post({ - parameter: { nextState }, - }) as Promise; - this.setInternalState(newState); - }, - subscribe: (callback: (nextState: PlatformAppState, previousState: PlatformAppState) => void) => { - return this.subscribe('State.*', callback); - }, - }; - } - - return { - get: () => this.localState[key], - set: (nextState: PlatformAppState[Key]) => { - const newState = this.stateMessageBus.post({ - parameter: { nextState }, - }) as Promise; - this.setInternalState(newState); - }, - subscribe: (callback: (nextState: PlatformAppState[Key], previousState: PlatformAppState[Key]) => void) => { - return this.subscribe(`State.${key}`, callback); - }, - }; - } - - subscribe( - eventName: EventNameParameter, - callback: EventCallbackParameter, - ): EventUnsubscribeFunction { - if (!(eventName in this.subscribeMap)) { - this.subscribeMap[eventName] = new Map(); - } - - this.subscribeMap[eventName].set(callback, true); - - return () => { - this.subscribeMap[eventName].delete(callback); - }; - } - - callSubscribedTopic( - eventName: EventNameParameter, - callbackParameters: PlatformAppEvent[EventName], - ) { - const callbackMap = this.subscribeMap[eventName]; - if (callbackMap && callbackMap?.size !== undefined) { - for (const [callback] of callbackMap.entries()) { - // @ts-expect-error if there are multiple parameters, we spread them in the callback call - callback(...(Array.isArray(callbackParameters) ? callbackParameters : [callbackParameters])); - } - } - } -} - -export interface IAppBridgePlatformApp< - State extends PlatformAppState = PlatformAppState, - Context extends PlatformAppContext = PlatformAppContext, - Event extends PlatformAppEvent = PlatformAppEvent, -> extends AppBridge { - api( - apiHandler: ApiHandlerParameter, - ): ApiReturn; - - dispatch( - dispatchHandler: DispatchHandlerParameter, - ): Promise; - - state(): StateReturn; - - state(key: Key): StateReturn; - - state(key?: keyof State | void): unknown; - - context(): ContextReturn; - - context(key: Key): ContextReturn; - - context(key?: keyof Context | void): unknown; - - subscribe( - eventName: EventNameParameter, - callback: EventCallbackParameter, - ): EventUnsubscribeFunction; -} diff --git a/packages/app-bridge/src/AppBridgeTheme.ts b/packages/app-bridge/src/AppBridgeTheme.ts deleted file mode 100644 index d6470c659..000000000 --- a/packages/app-bridge/src/AppBridgeTheme.ts +++ /dev/null @@ -1,357 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { - type ApiHandlerParameter, - type ApiMethodNameValidator, - type ApiReturn, - type AppBridge, - type CommandNameValidator, - type ContextAsEventName, - type ContextReturn, - type DispatchHandlerParameter, - type EventCallbackParameter, - type EventNameParameter, - type EventNameValidator, - type EventUnsubscribeFunction, - type StateAsEventName, - type StateReturn, -} from './AppBridge'; -import { type ApiMethodRegistry } from './registries/api/ApiMethodRegistry'; -import { type CommandRegistry } from './registries/commands/CommandRegistry'; -import { type EventRegistry } from './registries/events/EventRegistry'; -import { - type Asset, - type AssetChooserOptions, - type BrandportalLink, - type Color, - type ColorPalette, - type CoverPage, - type CoverPageCreate, - type CoverPageUpdate, - type CoverPageUpdateLegacy, - type Document, - type DocumentCategory, - type DocumentCategoryCreate, - type DocumentCategoryUpdate, - type DocumentGroup, - type DocumentGroupCreate, - type DocumentGroupUpdate, - type DocumentLibraryCreate, - type DocumentLibraryUpdate, - type DocumentLinkCreate, - type DocumentLinkUpdate, - type DocumentPage, - type DocumentPageCreate, - type DocumentPageTargets, - type DocumentPageUpdate, - type DocumentSection, - type DocumentStandardCreate, - type DocumentStandardUpdate, - type DocumentTargets, - type GuidelineSearchResult, - type Language, - type TargetsUpdate, - type ThemeTemplate, -} from './types'; - -export type ThemeApiMethod = ApiMethodNameValidator< - Pick ->; - -export type ThemeCommand = CommandNameValidator< - Pick< - CommandRegistry, - | 'closeAssetChooser' - | 'openAssetChooser' - | 'openNavigationManager' - | 'openSearchDialog' - | 'closeSearchDialog' - | 'navigateToDocumentSection' - > ->; - -export type TemplateContext = { templateId: string; type: ThemeTemplate } & ( - | { type: 'documentPage'; document: Document; documentPage: DocumentPage } - | { type: 'library'; document: Document } - | { type: 'cover'; coverPage: CoverPage } -); - -export type ThemeState = { - settings: Record; - assets: Record; -}; - -export type ThemeContext = { - brandId: number; - projectId: number; - portalId: number; - portalToken: string | null; - currentLanguage: string; - defaultLanguage: string; - isEditing: boolean; - isPublicLink: boolean; - isAuthenticated: boolean; - isSearchDialogOpen: boolean; - languages: Language[]; - template: TemplateContext | null; -}; - -export type ThemeEvent = EventNameValidator< - Pick & - StateAsEventName & - ContextAsEventName ->; - -export interface AppBridgeTheme< - State extends ThemeState = ThemeState, - Context extends ThemeContext = ThemeContext, - Event extends ThemeEvent = ThemeEvent, -> extends AppBridge { - api( - apiHandler: ApiHandlerParameter, - ): ApiReturn; - - dispatch( - dispatchHandler: DispatchHandlerParameter, - ): Promise; - - state(): StateReturn; - state(key: Key): StateReturn; - state(key?: keyof State | void): unknown; - - context(): ContextReturn; - context(key: Key): ContextReturn; - context(key?: keyof Context | void): unknown; - - subscribe( - eventName: EventNameParameter, - callback: EventCallbackParameter, - ): EventUnsubscribeFunction; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('portalId').get()` instead. - */ - getPortalId(): number; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('brandId').get()` instead. - */ - getBrandId(): number; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(openNavigationManager())` for opening the Navigation Manager. - */ - openNavigationManager(): void; - - getCoverPageTemplateSettings(): Promise; - - updateCoverPageTemplateSettings(settings: Record): Promise; - - getThemeSettings(): Promise; - - updateThemeSettings>(settings: ThemeSettings): Promise; - - getDocumentPageTemplateSettings(documentPageId: number): Promise; - - updateDocumentPageTemplateSettings(documentPageId: number, settings: Record): Promise; - - addAssetIdsToCoverPageTemplateAssetKey(key: string, assetIds: number[]): Promise>; - - getCoverPageTemplateAssets(): Promise>; - - deleteAssetIdsFromCoverPageTemplateAssetKey(key: string, assetIds: number[]): Promise; - - addAssetIdsToThemeAssetKey( - key: string, - assetIds: number[], - template: ThemeTemplate, - ): Promise>; - - getThemeAssets(template: ThemeTemplate): Promise>; - - deleteAssetIdsFromThemeAssetKey(key: string, assetIds: number[], template: ThemeTemplate): Promise; - - addAssetIdsToLibraryPageTemplateAssetKey( - documentId: number, - key: string, - assetIds: number[], - ): Promise>; - - getLibraryPageTemplateAssets(documentId: number): Promise>; - - deleteAssetIdsFromLibraryPageTemplateAssetKey(documentId: number, key: string, assetIds: number[]): Promise; - - addAssetIdsToDocumentPageTemplateAssetKey( - documentPageId: number, - key: string, - assetIds: number[], - ): Promise>; - - getDocumentPageTemplateAssets(documentPageId: number): Promise>; - - deleteAssetIdsFromDocumentPageTemplateAssetKey( - documentPageId: number, - key: string, - assetIds: number[], - ): Promise; - - getLibraryPageTemplateSettings(documentId: number): Promise; - - updateLibraryPageTemplateSettings(documentId: number, settings: Record): Promise; - - createLink(link: DocumentLinkCreate): Promise; - - updateLink(link: DocumentLinkUpdate): Promise; - - deleteLink(documentId: number): Promise; - - createLibrary(library: DocumentLibraryCreate): Promise; - - updateLibrary(library: DocumentLibraryUpdate): Promise; - - deleteLibrary(documentId: number): Promise; - - createStandardDocument(document: DocumentStandardCreate): Promise; - - updateStandardDocument(document: DocumentStandardUpdate): Promise; - - deleteStandardDocument(id: number): Promise; - - moveDocument(documentId: number, position: number, newDocumentGroupId?: number): Promise; - - createDocumentGroup(documentGroup: DocumentGroupCreate): Promise; - - updateDocumentGroup(documentGroup: DocumentGroupUpdate): Promise; - - deleteDocumentGroup(documentGroupId: number): Promise; - - moveDocumentGroup(documentGroupId: number, position: number): Promise; - - createDocumentCategory(category: DocumentCategoryCreate): Promise; - - updateDocumentCategory(category: DocumentCategoryUpdate): Promise; - - deleteDocumentCategory(documentCategoryId: number): Promise; - - moveDocumentCategory(documentCategoryId: number, documentId: number, position: number): Promise; - - createDocumentPage(documentPage: DocumentPageCreate): Promise; - - /** - * A method for page update - * - * @param documentPage - {@link DocumentPageUpdate} object - * @requires id - Indicates page identifier. - * - * - * and at least one of - * - * @property title - Indicates title of a page. - * @property documentId - Indicates to witch document the page belongs to. - * @property categoryId - Indicates to witch category the page belongs to. - * @property visibility - Indicates whether the page is visible only to the editor or everyone. - * @property linkUrl - Indicates whether the page is link or not. - */ - updateDocumentPage(documentPage: DocumentPageUpdate): Promise; - - deleteDocumentPage(documentPageId: number): Promise; - - moveDocumentPage( - documentPageId: number, - documentId: number, - position?: number, - documentCategoryId?: number, - ): Promise; - - duplicateDocumentPage(documentPageId: number): Promise; - - createCoverPage(coverPage: CoverPageCreate): Promise; - - updateCoverPage(coverPage: CoverPageUpdate): Promise; - - /** - * @deprecated legacy method, should be removed once new endpoint is available - */ - updateLegacyCoverPage(coverPage: CoverPageUpdateLegacy): Promise; - - deleteCoverPage(): Promise; - - updateBrandportalLink(brandportalLink: Partial): Promise | null>; - - getCoverPage(): Promise; - - getBrandportalLink(): Promise; - - getUngroupedDocuments(): Promise; - - updateDocumentTargets(targetIds: number[], documentIds: number[]): Promise; - - updateDocumentPageTargets(targetIds: number[], documentPageIds: number[]): Promise; - - /** - * Search in the current Guideline for a given query. - * @param query - The query to search for. - * @param order - The order in which the results should be returned. Defaults to 'relevance'. - */ - searchInGuideline(query: string, order?: 'relevance' | 'newest' | 'oldest'): Promise; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(openAssetChooser(options))` to open the asset chooser - * and `appBridge.subscribe('assetsChosen', callback)` to subscribe to the asset chosen event. - */ - openAssetChooser(callback: (selectedAssets: Asset[]) => void, options?: AssetChooserOptions): void; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.dispatch(closeAssetChooser())` instead. - */ - closeAssetChooser(): void; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('projectId').get()` instead. - */ - getProjectId(): number; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('isEditing').get()` instead. - */ - getEditorState(): boolean; - - /** - * @deprecated This will be removed in version 4.0.0 of `@frontify/app-bridge` - * Use `appBridge.context('language').get()` instead. - */ - getTranslationLanguage(): string; - - getColorPalettes(): Promise; - - getColorsByColorPaletteId(colorPaletteId: number): Promise; - - getAllDocuments(): Promise; - - getUngroupedDocuments(): Promise; - - getDocumentsByDocumentGroupId(documentGroupId: number): Promise; - - getDocumentGroups(): Promise; - - getDocumentPagesByDocumentId(documentId: number): Promise; - - getDocumentPagesByDocumentCategoryId(documentCategoryId: number): Promise; - - getDocumentCategoriesByDocumentId(documentId: number): Promise; - - getUncategorizedDocumentPagesByDocumentId(documentId: number): Promise; - - getDocumentSectionsByDocumentPageId(documentPageId: number): Promise; - - getDocumentTargets(documentId: number): Promise; - - getDocumentPageTargets(documentPageId: number): Promise; -} diff --git a/packages/app-bridge/src/errors/InitializationError.ts b/packages/app-bridge/src/errors/InitializationError.ts deleted file mode 100644 index 2cfbf5bc8..000000000 --- a/packages/app-bridge/src/errors/InitializationError.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export class InitializationError extends Error { - constructor(message?: string) { - super(`Initialization failed. ${message}`); - this.name = 'InitializationError'; - } -} diff --git a/packages/app-bridge/src/errors/NotifyError.ts b/packages/app-bridge/src/errors/NotifyError.ts deleted file mode 100644 index 0057a78fd..000000000 --- a/packages/app-bridge/src/errors/NotifyError.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export class FetchError extends Error { - constructor(topic: string) { - super(`Call with topic ${topic} failed.`); - this.name = 'FetchError'; - } -} diff --git a/packages/app-bridge/src/errors/TimeoutReachedError.ts b/packages/app-bridge/src/errors/TimeoutReachedError.ts deleted file mode 100644 index 0044ca987..000000000 --- a/packages/app-bridge/src/errors/TimeoutReachedError.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export class TimeoutReachedError extends Error { - constructor(topic: string) { - super(`Timeout for call with topic "${topic}" expired. Call was aborted.`); - this.name = 'TimeoutReachedError'; - } -} diff --git a/packages/app-bridge/src/errors/ValidationError.ts b/packages/app-bridge/src/errors/ValidationError.ts deleted file mode 100644 index 3872fcb12..000000000 --- a/packages/app-bridge/src/errors/ValidationError.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export class ValidationError extends Error { - constructor(message?: string) { - super(`Validation failed. ${message}`); - this.name = 'ValidationError'; - } -} diff --git a/packages/app-bridge/src/errors/index.ts b/packages/app-bridge/src/errors/index.ts index d90f172b4..426eb42d8 100644 --- a/packages/app-bridge/src/errors/index.ts +++ b/packages/app-bridge/src/errors/index.ts @@ -1,7 +1,3 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ export * from './HttpClientError'; -export * from './InitializationError'; -export * from './NotifyError'; -export * from './TimeoutReachedError'; -export * from './ValidationError'; diff --git a/packages/app-bridge/src/helpers/index.ts b/packages/app-bridge/src/helpers/index.ts index 0e51a26f7..3278ad31a 100644 --- a/packages/app-bridge/src/helpers/index.ts +++ b/packages/app-bridge/src/helpers/index.ts @@ -1,4 +1,3 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ -export * from './link'; export * from './navigation'; diff --git a/packages/app-bridge/src/helpers/link.spec.ts b/packages/app-bridge/src/helpers/link.spec.ts deleted file mode 100644 index 30605a1eb..000000000 --- a/packages/app-bridge/src/helpers/link.spec.ts +++ /dev/null @@ -1,272 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { DocumentCategoryDummy, DocumentDummy, DocumentPageDummy } from '../tests'; -import { CoverPageDummy } from '../tests/CoverPageDummy'; - -import { computeCoverPageLink, computeDocumentLink, computeDocumentPageLink } from './link'; - -const PORTAL_ID = 23451; -const PORTAL_TOKEN = 'weisdfjsdf'; -const DOCUMENT_ID = 2937; -const DOCUMENT_CATEGORY_ID = 2314; -const DOCUMENT_PAGE_ID = 9382; - -describe('computeCoverPageLink', () => { - it('should return the correct link when private', () => { - const coverPage = CoverPageDummy.with(PORTAL_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: null }); - - expect(result).toEqual(`/hub/${PORTAL_ID}`); - }); - - it('should return the correct link when private with same language as default', () => { - const coverPage = CoverPageDummy.with(PORTAL_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: null }); - - expect(result).toEqual(`/hub/${PORTAL_ID}`); - }); - - it('should return the correct link when private with different language from default', () => { - const coverPage = { ...CoverPageDummy.with(PORTAL_ID), url: `/hub/${PORTAL_ID}/fr` }; - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: null }); - - expect(result).toEqual(`/hub/${PORTAL_ID}/fr`); - }); - - it('should return the correct link when publicly shared', () => { - const coverPage = CoverPageDummy.with(PORTAL_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: PORTAL_TOKEN }); - - expect(result).toEqual(`/d/${PORTAL_TOKEN}`); - }); - - it('should return the correct link when publicly shared with same language as default', () => { - const coverPage = CoverPageDummy.with(PORTAL_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: PORTAL_TOKEN }); - - expect(result).toEqual(`/d/${PORTAL_TOKEN}`); - }); - - it('should return the correct link when publicly shared with different language from default', () => { - const coverPage = CoverPageDummy.with(PORTAL_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeCoverPageLink({ coverPage, language, portalToken: PORTAL_TOKEN }); - - expect(result).toEqual(`/d/${PORTAL_TOKEN}/fr`); - }); -}); - -describe('computeDocumentLink', () => { - it('should return the correct link when private', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}`); - }); - - it('should return the correct link when private with same language as default', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}`); - }); - - it('should return the correct link when private with different language from default', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}/fr`); - }); - - it('should return the correct link when publicly shared', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}`); - }); - - it('should return the correct link when publicly shared with same language as default', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}`); - }); - - it('should return the correct link when publicly shared with different language from default', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentLink({ document, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/fr/${document.slug}`); - }); -}); - -describe('computeDocumentPageLink', () => { - it('should return the correct link when private (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}#/-/${documentPage.slug}`); - }); - - it('should return the correct link when private (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: null, - }); - expect(result).toEqual(`/document/${DOCUMENT_ID}#/${documentCategory.slug}/${documentPage.slug}`); - }); - - it('should return the correct link when private with same language as default (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}#/-/${documentPage.slug}`); - }); - - it('should return the correct link when private with same language as default (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: null, - }); - expect(result).toEqual(`/document/${DOCUMENT_ID}#/${documentCategory.slug}/${documentPage.slug}`); - }); - - it('should return the correct link when private with different language from default (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: null }); - expect(result).toEqual(`/document/${DOCUMENT_ID}/fr#/-/${documentPage.slug}`); - }); - - it('should return the correct link when private with different language from default (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: null, - }); - expect(result).toEqual(`/document/${DOCUMENT_ID}/fr#/${documentCategory.slug}/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}#/-/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: PORTAL_TOKEN, - }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}#/${documentCategory.slug}/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared with same language as default (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}#/-/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared with same language as default (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'en', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: PORTAL_TOKEN, - }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/${document.slug}#/${documentCategory.slug}/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared with different language from default (without category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ document, documentPage, language, portalToken: PORTAL_TOKEN }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/fr/${document.slug}#/-/${documentPage.slug}`); - }); - - it('should return the correct link when publicly shared with different language from default (with category)', () => { - const document = DocumentDummy.with(DOCUMENT_ID); - const documentCategory = DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID); - const documentPage = DocumentPageDummy.with(DOCUMENT_PAGE_ID); - const language = { currentLanguage: 'fr', defaultLanguage: 'en' }; - - const result = computeDocumentPageLink({ - document, - documentCategory, - documentPage, - language, - portalToken: PORTAL_TOKEN, - }); - expect(result).toEqual(`/d/${PORTAL_TOKEN}/fr/${document.slug}#/${documentCategory.slug}/${documentPage.slug}`); - }); -}); diff --git a/packages/app-bridge/src/helpers/link.ts b/packages/app-bridge/src/helpers/link.ts deleted file mode 100644 index f66e95d1a..000000000 --- a/packages/app-bridge/src/helpers/link.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type CoverPage, type Document, type DocumentCategory, type DocumentPage } from '../types'; - -export const computeCoverPageLink = ({ - coverPage, - language, - portalToken, -}: { - coverPage: CoverPage; - language: { - currentLanguage: string; - defaultLanguage: string; - }; - portalToken: Nullable; -}) => { - const languageUrlPart = language.currentLanguage === language.defaultLanguage ? '' : `/${language.currentLanguage}`; - - if (portalToken) { - return `/d/${portalToken}${languageUrlPart}`; - } - - return coverPage.url; -}; - -export const computeDocumentLink = ({ - document, - language, - portalToken, -}: { - document: Document; - language: { - currentLanguage: string; - defaultLanguage: string; - }; - portalToken: Nullable; -}) => { - const languageUrlPart = language.currentLanguage === language.defaultLanguage ? '' : `/${language.currentLanguage}`; - - if (portalToken) { - return `/d/${portalToken}${languageUrlPart}/${document.slug}`; - } - - return `/document/${document.id}${languageUrlPart}`; -}; - -export const computeDocumentPageLink = ({ - documentPage, - language, - portalToken, - document, - documentCategory, -}: { - documentPage: DocumentPage; - document: Document; - documentCategory?: DocumentCategory; - language: { - currentLanguage: string; - defaultLanguage: string; - }; - portalToken: Nullable; -}) => { - const categoryUrlPart = documentCategory ? `${documentCategory.slug}/` : '-/'; - const languageUrlPart = language.currentLanguage === language.defaultLanguage ? '' : `/${language.currentLanguage}`; - if (portalToken) { - return `/d/${portalToken}${languageUrlPart}/${document.slug}#/${categoryUrlPart}${documentPage.slug}`; - } - - return `/document/${document.id}${languageUrlPart}#/${categoryUrlPart}${documentPage.slug}`; -}; diff --git a/packages/app-bridge/src/helpers/navigation.spec.ts b/packages/app-bridge/src/helpers/navigation.spec.ts index b5e79f065..5ebf4ee28 100644 --- a/packages/app-bridge/src/helpers/navigation.spec.ts +++ b/packages/app-bridge/src/helpers/navigation.spec.ts @@ -7,7 +7,6 @@ import { DocumentSectionDummy } from '../tests/DocumentSectionDummy'; import { filterDocumentSectionsWithUnreadableTitles } from './navigation'; const documentSections = [ - // @ts-expect-error On purpose to test the function DocumentSectionDummy.withFields({ id: 464, title: null }), DocumentSectionDummy.withFields({ id: 356, title: 'New Title' }), DocumentSectionDummy.withFields({ id: 37675, title: ' ' }), diff --git a/packages/app-bridge/src/index.ts b/packages/app-bridge/src/index.ts index d2022f0d7..2fc879359 100644 --- a/packages/app-bridge/src/index.ts +++ b/packages/app-bridge/src/index.ts @@ -6,9 +6,6 @@ enableMapSet(); export * from './AppBridge'; export * from './AppBridgeBlock'; -export * from './AppBridgeCreateAsset'; -export * from './AppBridgePlatformApp'; -export * from './AppBridgeTheme'; export * from './errors'; export * from './helpers'; export * from './registries'; diff --git a/packages/app-bridge/src/react/index.ts b/packages/app-bridge/src/react/index.ts index 11220d580..e0f943624 100644 --- a/packages/app-bridge/src/react/index.ts +++ b/packages/app-bridge/src/react/index.ts @@ -8,11 +8,9 @@ export * from './useAssetViewer'; export * from './useBlockAssets'; export * from './useBlockSettings'; export * from './useBlockTemplates'; -export * from './useBrandportalLink'; export * from './useCategorizedDocumentPages'; export * from './useColorPalettes'; export * from './useColors'; -export * from './useCoverPage'; export * from './useDocumentCategories'; export * from './useDocumentGroups'; export * from './useDocumentPageTargets'; @@ -22,18 +20,8 @@ export * from './useEditorState'; export * from './useFileInput'; export * from './useFileUpload'; export * from './useGroupedDocuments'; -export * from './useGuidelineActions'; -export * from './useLanguage'; -export * from './useNavigationManager'; -export * from './usePageTemplateSettings'; -export * from './usePlatformAppBridge'; export * from './usePrivacySettings'; export * from './useReadyForPrint'; -export * from './useTemplateAssets'; export * from './useTemplateChooser'; -export * from './useThemeAssets'; -export * from './useThemeSettings'; export * from './useUncategorizedDocumentPages'; export * from './useUngroupedDocuments'; -export * from './usePortalNavigation'; -export * from './useDocumentNavigation'; diff --git a/packages/app-bridge/src/react/useBrandportalLink.spec.ts b/packages/app-bridge/src/react/useBrandportalLink.spec.ts deleted file mode 100644 index 8839cb0a9..000000000 --- a/packages/app-bridge/src/react/useBrandportalLink.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -// @vitest-environment happy-dom - -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, renderHook } from '@testing-library/react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { BrandportalLinkDummy, getAppBridgeThemeStub } from '../tests'; - -import { useBrandportalLink } from './useBrandportalLink'; - -describe('useBrandportalLink', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should return the brandportal link from appBridge', async () => { - const appBridge = getAppBridgeThemeStub(); - - const brandportalLink = BrandportalLinkDummy.with(); - appBridge.getBrandportalLink.resolves(brandportalLink); - - const { result } = renderHook(() => useBrandportalLink(appBridge)); - - expect(result.current.brandportalLink).toBe(null); - - await act(async () => { - await appBridge.getBrandportalLink(); - }); - - expect(result.current.brandportalLink).toEqual(brandportalLink); - }); - - it('should update the brandportal link when an event is emitted', () => { - const appBridge = getAppBridgeThemeStub(); - const { result } = renderHook(() => useBrandportalLink(appBridge)); - - const updatedBrandportalLink = BrandportalLinkDummy.with({ - label: 'Updated Brandportal Link', - }); - - act(() => { - window.emitter.emit('AppBridge:GuidelineBrandportalLink:Action', { - action: 'update', - brandportalLink: updatedBrandportalLink, - }); - }); - - expect(result.current.brandportalLink).toEqual(updatedBrandportalLink); - }); - - it('should not update the brandportal link when an event with an invalid action is emitted', () => { - const appBridge = getAppBridgeThemeStub(); - const { result } = renderHook(() => useBrandportalLink(appBridge)); - - result.current.brandportalLink = BrandportalLinkDummy.with(); - - act(() => { - window.emitter.emit('AppBridge:GuidelineBrandportalLink:Action', { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - action: 'invalid' as any, - brandportalLink: { enabled: false, label: '', url: '' }, - }); - }); - - expect(result.current.brandportalLink).toEqual(BrandportalLinkDummy.with()); - }); - - it('should start fetching only when it is enabled', () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getBrandportalLink'); - - let enabled = false; - - const { rerender } = renderHook(() => useBrandportalLink(appBridge, { enabled })); - - expect(spy).not.toBeCalled(); - - enabled = true; - rerender(); - - expect(spy).toBeCalled(); - }); - - it('should unregister when unmounted', () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(window.emitter, 'off'); - - const { unmount } = renderHook(() => useBrandportalLink(appBridge)); - - unmount(); - - expect(spy).toBeCalledWith('AppBridge:GuidelineBrandportalLink:Action', expect.any(Function)); - }); -}); diff --git a/packages/app-bridge/src/react/useBrandportalLink.ts b/packages/app-bridge/src/react/useBrandportalLink.ts deleted file mode 100644 index 296c622be..000000000 --- a/packages/app-bridge/src/react/useBrandportalLink.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type BrandportalLink, type EmitterAction } from '../types'; - -const defaultState: BrandportalLink = { - enabled: false, - label: '', - url: '', -}; - -export type UseBrandportalLinkReturnType = { - brandportalLink: Nullable; - isLoading: boolean; -}; - -type Options = { - /** - * Whether it should fetch on mount. - */ - enabled?: boolean; -}; - -export const useBrandportalLink = ( - appBridge: AppBridgeTheme, - options: Options = { enabled: true }, -): UseBrandportalLinkReturnType => { - const [brandportalLink, setBrandportalLink] = useState>(null); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const fetchBrandportalLink = async () => { - setIsLoading(true); - setBrandportalLink(await appBridge.getBrandportalLink()); - setIsLoading(false); - }; - - if (options.enabled) { - fetchBrandportalLink().catch(console.error); - } - }, [appBridge, options.enabled]); - - useEffect(() => { - const updateBrandportalLinkFromEvent = (event: { - brandportalLink: Partial; - action: Extract; - }) => { - setBrandportalLink((previousState) => { - if (event.action === 'update') { - return { ...defaultState, ...previousState, ...event.brandportalLink }; - } - - return previousState; - }); - }; - - window.emitter.on('AppBridge:GuidelineBrandportalLink:Action', updateBrandportalLinkFromEvent); - - return () => { - window.emitter.off('AppBridge:GuidelineBrandportalLink:Action', updateBrandportalLinkFromEvent); - }; - }, [appBridge]); - - return { brandportalLink, isLoading }; -}; diff --git a/packages/app-bridge/src/react/useCategorizedDocumentPages.spec.ts b/packages/app-bridge/src/react/useCategorizedDocumentPages.spec.ts index 6912bf7b7..bd21b93d4 100644 --- a/packages/app-bridge/src/react/useCategorizedDocumentPages.spec.ts +++ b/packages/app-bridge/src/react/useCategorizedDocumentPages.spec.ts @@ -5,7 +5,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { DocumentPageDummy, getAppBridgeThemeStub } from '../tests'; +import { DocumentPageDummy, getAppBridgeBlockStub } from '../tests'; import { type DocumentPage } from '../types'; import { useCategorizedDocumentPages } from './useCategorizedDocumentPages'; @@ -24,7 +24,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should fetch document pages on mount', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const { result } = renderHook(() => useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID)); @@ -45,7 +45,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should not fetch document pages on mount if not enabled', () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const { result } = renderHook(() => @@ -58,7 +58,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should fetch document pages if it gets enabled', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); let enabled = false; @@ -90,7 +90,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should update document pages if a page is added in the category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const DOCUMENT_PAGE = DocumentPageDummy.withFields({ @@ -137,7 +137,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should not update the document pages if a page is added in another category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const DOCUMENT_PAGE = DocumentPageDummy.withFields({ @@ -165,7 +165,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should update document pages if a page is removed from the category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const DOCUMENT_PAGE = DocumentPageDummy.withFields({ @@ -203,7 +203,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should not update the document pages if a page is removed from another category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const DOCUMENT_PAGE = DocumentPageDummy.withFields({ @@ -242,7 +242,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should update document pages if a page is updated in the category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const UPDATED_DOCUMENT_PAGE: DocumentPage = { @@ -287,7 +287,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should not update the document pages if a page is updated in another category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const UPDATED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ @@ -322,7 +322,7 @@ describe('useCategorizedDocumentPages', () => { }); it('should update document pages if a page is moved in the category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); const UPDATED_DOCUMENT_PAGE: DocumentPage = { diff --git a/packages/app-bridge/src/react/useCategorizedDocumentPages.ts b/packages/app-bridge/src/react/useCategorizedDocumentPages.ts index 3f334f6b7..fad8d9a79 100644 --- a/packages/app-bridge/src/react/useCategorizedDocumentPages.ts +++ b/packages/app-bridge/src/react/useCategorizedDocumentPages.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentPage, type EmitterEvents } from '../types'; import { type DocumentPageTargetEvent } from './useDocumentPageTargets'; @@ -21,7 +20,7 @@ type Options = { const sortDocumentPages = (a: DocumentPage, b: DocumentPage) => (a.sort && b.sort ? a.sort - b.sort : 0); export const useCategorizedDocumentPages = ( - appBridge: AppBridgeBlock | AppBridgeTheme, + appBridge: AppBridgeBlock, documentCategoryId: number, options: Options = { enabled: true }, ) => { @@ -161,10 +160,7 @@ const moveDocumentPage = (draft: Map, documentPage: Docume return draft; }; -const fetchDocumentPagesByDocumentCategoryId = async ( - appBridge: AppBridgeBlock | AppBridgeTheme, - documentCategoryId: number, -) => { +const fetchDocumentPagesByDocumentCategoryId = async (appBridge: AppBridgeBlock, documentCategoryId: number) => { const pages = await appBridge.getDocumentPagesByDocumentCategoryId(documentCategoryId); return new Map([...pages].sort(sortDocumentPages).map((page) => [page.id, page])); }; diff --git a/packages/app-bridge/src/react/useCoverPage.spec.ts b/packages/app-bridge/src/react/useCoverPage.spec.ts deleted file mode 100644 index c56c9563d..000000000 --- a/packages/app-bridge/src/react/useCoverPage.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -// @vitest-environment happy-dom - -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, renderHook, waitFor } from '@testing-library/react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { CoverPageDummy, getAppBridgeThemeStub } from '../tests'; -import { type CoverPage, type CoverPageUpdate } from '../types'; - -import { useCoverPage } from './useCoverPage'; - -describe('useCoverPage', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should return the cover page from appBridge', async () => { - const appBridge = getAppBridgeThemeStub(); - - const coverPage = CoverPageDummy.with(1); - appBridge.getCoverPage.resolves(coverPage); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useCoverPage(appBridge)); - - expect(result.current.coverPage).toBe(null); - - await act(async () => { - await appBridge.getCoverPage(); - }); - - expect(result.current.coverPage).toEqual(coverPage); - }); - - it('should create the cover page when an event is emitted', async () => { - const appBridge = getAppBridgeThemeStub(); - - const coverPage = CoverPageDummy.with(243); - appBridge.getCoverPage.resolves(coverPage); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useCoverPage(appBridge)); - - act(() => { - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - action: 'add', - coverPage, - }); - }); - - await waitFor(() => expect(result.current.coverPage).toEqual(coverPage)); - }); - - it('should update the cover page when an event is emitted', () => { - const appBridge = getAppBridgeThemeStub(); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useCoverPage(appBridge)); - - const updatedCoverPage: CoverPageUpdate = { id: 1, title: 'Updated Cover Page' }; - - act(() => { - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - action: 'update', - coverPage: updatedCoverPage as CoverPage, - }); - }); - - expect(result.current.coverPage).toEqual(updatedCoverPage); - }); - - it('should set the cover page to null when an event to delete it is emitted', () => { - const appBridge = getAppBridgeThemeStub(); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useCoverPage(appBridge)); - - result.current.coverPage = null; - - act(() => { - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { action: 'delete' }); - }); - - expect(result.current.coverPage).toBe(null); - }); - - it('should not update the cover page when an event with an invalid action is emitted', () => { - const appBridge = getAppBridgeThemeStub(); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useCoverPage(appBridge)); - - result.current.coverPage = CoverPageDummy.with(1); - - act(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { action: 'invalid' as any }); - }); - - expect(result.current.coverPage).toEqual(CoverPageDummy.with(1)); - }); - - it('should start fetching only when it is enabled', () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getCoverPage'); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - let enabled = false; - - const { rerender } = renderHook(() => useCoverPage(appBridge, { enabled })); - - expect(spy).not.toBeCalled(); - - enabled = true; - rerender(); - - expect(spy).toBeCalled(); - }); -}); diff --git a/packages/app-bridge/src/react/useCoverPage.ts b/packages/app-bridge/src/react/useCoverPage.ts deleted file mode 100644 index 61d19f367..000000000 --- a/packages/app-bridge/src/react/useCoverPage.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useCallback, useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type CoverPage, type EmitterAction } from '../types'; - -export type UseCoverPageReturnType = { - coverPage: Nullable; - isLoading: boolean; -}; - -type Options = { - /** - * Whether it should fetch on mount. - */ - enabled?: boolean; -}; - -export const useCoverPage = ( - appBridge: AppBridgeTheme, - options: Options = { enabled: true }, -): UseCoverPageReturnType => { - const [coverPage, setCoverPage] = useState>(null); - const [isLoading, setIsLoading] = useState(true); - - const fetchCoverPage = useCallback(async () => { - setIsLoading(true); - setCoverPage(await appBridge.getCoverPage()); - setIsLoading(false); - }, [appBridge]); - - useEffect(() => { - if (options.enabled) { - fetchCoverPage().catch(console.error); - } - }, [appBridge, fetchCoverPage, options.enabled]); - - useEffect(() => { - const updateCoverPageFromEvent = (event: { action: EmitterAction; coverPage?: CoverPage }) => { - if (event.action === 'add') { - fetchCoverPage().catch(console.error); - return; - } - - setCoverPage((previousState) => { - if (event.action === 'delete') { - return null; - } - - if (event.action === 'update' && event.coverPage) { - return { ...previousState, ...event.coverPage }; - } - - return previousState; - }); - }; - - window.emitter.on('AppBridge:GuidelineCoverPage:Action', updateCoverPageFromEvent); - - return () => { - window.emitter.off('AppBridge:GuidelineCoverPage:Action', updateCoverPageFromEvent); - }; - }, [appBridge, fetchCoverPage]); - - return { coverPage, isLoading }; -}; diff --git a/packages/app-bridge/src/react/useDocumentCategories.spec.ts b/packages/app-bridge/src/react/useDocumentCategories.spec.ts index 47717d4e8..271099d7f 100644 --- a/packages/app-bridge/src/react/useDocumentCategories.spec.ts +++ b/packages/app-bridge/src/react/useDocumentCategories.spec.ts @@ -5,7 +5,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { DocumentCategoryDummy, getAppBridgeThemeStub } from '../tests'; +import { DocumentCategoryDummy, getAppBridgeBlockStub } from '../tests'; import { type DocumentCategory } from '../types'; import { useDocumentCategories } from './useDocumentCategories'; @@ -24,7 +24,7 @@ describe('useDocumentCategories', () => { }); it('should fetch document categories on mount', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID)); @@ -44,7 +44,7 @@ describe('useDocumentCategories', () => { }); it('should not fetch document categories on mount if not enabled', () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID, { enabled: false })); @@ -55,7 +55,7 @@ describe('useDocumentCategories', () => { }); it('should fetch document categories if it gets enabled', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); let enabled = false; @@ -84,7 +84,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories if a category is added', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY = DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages( @@ -130,7 +130,7 @@ describe('useDocumentCategories', () => { }); it('should not update document categories if a category is added to another document', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY = DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages( @@ -165,7 +165,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories if a category is removed', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY_TO_DELETE = DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages( @@ -202,7 +202,7 @@ describe('useDocumentCategories', () => { }); it('should not update document categories if a category is removed from another document', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY_TO_DELETE = DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages( @@ -240,7 +240,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories if a category is updated', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY_TO_UPDATE: DocumentCategory = { @@ -283,7 +283,7 @@ describe('useDocumentCategories', () => { }); it('should not update document categories if a category is updated from another document', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const DOCUMENT_CATEGORY_TO_UPDATE: DocumentCategory = { @@ -321,7 +321,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories number if a document page is added to a category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID)); @@ -339,7 +339,7 @@ describe('useDocumentCategories', () => { // Trigger a "document page add" event in the specified document window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { action: 'add', - documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_2 }, + documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_2, documentId: DOCUMENT_ID }, }); await waitFor(() => { @@ -352,7 +352,7 @@ describe('useDocumentCategories', () => { }); it('should not update document categories if a document page is added to another category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID)); @@ -370,7 +370,7 @@ describe('useDocumentCategories', () => { // Trigger a "document page add" event in the specified document window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { action: 'add', - documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_4 }, + documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_4, documentId: DOCUMENT_ID }, }); await waitFor(() => { @@ -383,7 +383,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories if a document page is removed from a category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID)); @@ -401,7 +401,7 @@ describe('useDocumentCategories', () => { // Trigger a "document page delete" event in the specified document window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { action: 'delete', - documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_3 }, + documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_3, documentId: DOCUMENT_ID }, }); await waitFor(() => { @@ -414,7 +414,7 @@ describe('useDocumentCategories', () => { }); it('should not update document categories if a document page is removed from another category', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const { result } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID)); @@ -432,7 +432,7 @@ describe('useDocumentCategories', () => { // Trigger a "document page delete" event in the specified document window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { action: 'delete', - documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_4 }, + documentPage: { id: DOCUMENT_PAGE_ID, categoryId: DOCUMENT_CATEGORY_ID_4, documentId: DOCUMENT_ID }, }); await waitFor(() => { @@ -445,7 +445,7 @@ describe('useDocumentCategories', () => { }); it('should update document categories sort if a category is moved', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); const CATEGORY_1 = DocumentCategoryDummy.withFields({ diff --git a/packages/app-bridge/src/react/useDocumentCategories.ts b/packages/app-bridge/src/react/useDocumentCategories.ts index a644e77b3..2e82b48eb 100644 --- a/packages/app-bridge/src/react/useDocumentCategories.ts +++ b/packages/app-bridge/src/react/useDocumentCategories.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentCategory, type EmitterEvents } from '../types'; type DocumentPageEvent = EmitterEvents['AppBridge:GuidelineDocumentCategory:DocumentPageAction']; @@ -20,7 +19,7 @@ type Options = { const sortDocumentCategories = (a: DocumentCategory, b: DocumentCategory) => (a.sort && b.sort ? a.sort - b.sort : 0); export const useDocumentCategories = ( - appBridge: AppBridgeBlock | AppBridgeTheme, + appBridge: AppBridgeBlock, documentId: number, options: Options = { enabled: true }, ) => { @@ -186,7 +185,7 @@ const actionHandlers = { default: (documentCategories: Map) => documentCategories, }; -const fetchDocumentCategories = async (appBridge: AppBridgeBlock | AppBridgeTheme, documentId: number) => { +const fetchDocumentCategories = async (appBridge: AppBridgeBlock, documentId: number) => { const categories = await appBridge.getDocumentCategoriesByDocumentId(documentId); return new Map([...categories].sort(sortDocumentCategories).map((category) => [category.id, category])); }; diff --git a/packages/app-bridge/src/react/useDocumentGroups.ts b/packages/app-bridge/src/react/useDocumentGroups.ts index f1eaa586e..4e2430141 100644 --- a/packages/app-bridge/src/react/useDocumentGroups.ts +++ b/packages/app-bridge/src/react/useDocumentGroups.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentGroup, type EmitterEvents } from '../types'; type DocumentGroupDocumentEvent = EmitterEvents['AppBridge:GuidelineDocumentGroup:DocumentAction']; @@ -21,7 +20,7 @@ type Options = { const sortDocumentGroups = (a: DocumentGroup, b: DocumentGroup) => (a.sort && b.sort ? a.sort - b.sort : 0); -export const useDocumentGroups = (appBridge: AppBridgeBlock | AppBridgeTheme, options: Options = { enabled: true }) => { +export const useDocumentGroups = (appBridge: AppBridgeBlock, options: Options = { enabled: true }) => { const [documentGroups, setDocumentGroups] = useState>(new Map([])); const [isLoading, setIsLoading] = useState(true); @@ -213,7 +212,7 @@ const actionHandlers = { default: (groups: Map) => groups, }; -const fetchDocumentGroups = async (appBridge: AppBridgeBlock | AppBridgeTheme) => { +const fetchDocumentGroups = async (appBridge: AppBridgeBlock) => { const documentGroups = await appBridge.getDocumentGroups(); return new Map([...documentGroups].sort(sortDocumentGroups).map((group) => [group.id, group])); }; diff --git a/packages/app-bridge/src/react/useDocumentNavigation.spec.ts b/packages/app-bridge/src/react/useDocumentNavigation.spec.ts deleted file mode 100644 index a5ccf2bb3..000000000 --- a/packages/app-bridge/src/react/useDocumentNavigation.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import { type DocumentPage, type DocumentSection, type GuidelineDocument, type DocumentCategory } from 'src/types'; - -import { getAppBridgeThemeStub, DocumentNavigationTreeDummy } from '../tests'; - -import { useDocumentNavigation } from './useDocumentNavigation'; - -describe('useDocumentNavigation', () => { - const testInitialRender = async ({ - expectRefetch, - options, - }: { - expectRefetch: boolean; - options?: { enabled: boolean }; - }) => { - const document = { id: () => 1138 }; - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api').mockResolvedValueOnce(DocumentNavigationTreeDummy.default()); - - const { result } = renderHook(() => useDocumentNavigation(appBridge, document, options)); - - expect(result.current.isLoading).toBe(true); - - await waitFor(() => { - expect(result.current.isLoading).toBe(!expectRefetch); - expect(result.current.navigationItems).toEqual(expectRefetch ? DocumentNavigationTreeDummy.default() : []); - }); - - if (expectRefetch) { - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getDocumentNavigation', payload: { document } }); - } - }; - - const testEventHandler = async (emitEvent: () => void) => { - const document = { id: () => 1138 }; - - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api').mockResolvedValue(DocumentNavigationTreeDummy.default()); - - const { result } = renderHook(() => useDocumentNavigation(appBridge, document)); - - expect(result.current.isLoading).toBe(true); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DocumentNavigationTreeDummy.default()); - }); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getDocumentNavigation', payload: { document } }); - - spy.mockResolvedValueOnce(DocumentNavigationTreeDummy.alternative()); - - emitEvent(); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledTimes(2); - expect(result.current.navigationItems).toEqual(DocumentNavigationTreeDummy.alternative()); - }); - }; - - it('should not refetch on render when enabled is set to false', () => - testInitialRender({ expectRefetch: false, options: { enabled: false } })); - - it('should refetch on render when enabled is set to true', () => - testInitialRender({ expectRefetch: true, options: { enabled: true } })); - - it('should refetch on render when no options are given', () => - testInitialRender({ expectRefetch: true, options: undefined })); - - it('should refetch when the "AppBridge:GuidelineDocumentCategory:Action" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:Action', { - action: 'add', - documentCategory: {}, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentCategory:DocumentPageAction" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { - action: 'add', - documentPage: { categoryId: 0, id: 0 }, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentCategory:MoveEvent" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:MoveEvent', { - action: 'movePreview', - documentId: 0, - documentCategory: {}, - position: 0, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentPage:Action" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'add', - documentPage: {}, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentPage:MoveEvent" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentPage:MoveEvent', { - action: 'movePreview', - documentId: 0, - documentPage: {}, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentPageTargets:Action" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentPageTargets:Action', { - action: 'update', - payload: { targets: [], pageIds: [] }, - }); - })); - - it('should refetch when the "AppBridge:GuidelineDocumentSection:Action" event occurs', () => - testEventHandler(() => { - window.emitter.emit('AppBridge:GuidelineDocumentSection:Action', { - action: 'add', - payload: { documentPageId: 0, documentSection: {}, previousDocumentSectionId: 0 }, - }); - })); -}); diff --git a/packages/app-bridge/src/react/useDocumentNavigation.ts b/packages/app-bridge/src/react/useDocumentNavigation.ts deleted file mode 100644 index ec38e0f51..000000000 --- a/packages/app-bridge/src/react/useDocumentNavigation.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import debounce from 'lodash-es/debounce'; -import { useCallback, useEffect, useMemo, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type GuidelineDocument, type DocumentNavigationItem } from '../types/Guideline'; - -type Options = { - /** - * Whether it should fetch on mount. - */ - enabled?: boolean; -}; - -export const useDocumentNavigation = ( - appBridge: AppBridgeTheme, - document: GuidelineDocument, - options: Options = { enabled: true }, -) => { - const [navigationItems, setNavigationItems] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const refetch = useCallback(async () => { - setIsLoading(true); - setNavigationItems(await appBridge.api({ name: 'getDocumentNavigation', payload: { document } })); - setIsLoading(false); - }, [appBridge, document]); - - useEffect(() => { - if (options.enabled) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refetch(); - } - }, [options.enabled, refetch]); - - const debouncedRefetch = useMemo( - () => - debounce(() => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refetch(); - }, 100), - [refetch], - ); - - useEffect(() => { - window.emitter.on('AppBridge:GuidelineDocumentCategory:Action', debouncedRefetch); - window.emitter.on('AppBridge:GuidelineDocumentCategory:DocumentPageAction', debouncedRefetch); - window.emitter.on('AppBridge:GuidelineDocumentCategory:MoveEvent', debouncedRefetch); - - window.emitter.on('AppBridge:GuidelineDocumentPage:Action', debouncedRefetch); - window.emitter.on('AppBridge:GuidelineDocumentPage:MoveEvent', debouncedRefetch); - window.emitter.on('AppBridge:GuidelineDocumentPageTargets:Action', debouncedRefetch); - - window.emitter.on('AppBridge:GuidelineDocumentSection:Action', debouncedRefetch); - - return () => { - window.emitter.off('AppBridge:GuidelineDocumentCategory:Action', debouncedRefetch); - window.emitter.off('AppBridge:GuidelineDocumentCategory:DocumentPageAction', debouncedRefetch); - window.emitter.off('AppBridge:GuidelineDocumentCategory:MoveEvent', debouncedRefetch); - - window.emitter.off('AppBridge:GuidelineDocumentPage:Action', debouncedRefetch); - window.emitter.off('AppBridge:GuidelineDocumentPage:MoveEvent', debouncedRefetch); - window.emitter.off('AppBridge:GuidelineDocumentPageTargets:Action', debouncedRefetch); - - window.emitter.off('AppBridge:GuidelineDocumentSection:Action', debouncedRefetch); - }; - }, [navigationItems, debouncedRefetch]); - - return { navigationItems, refetch, isLoading }; -}; diff --git a/packages/app-bridge/src/react/useDocumentPageTargets.spec.ts b/packages/app-bridge/src/react/useDocumentPageTargets.spec.ts index 3e9358f08..f4a8a5529 100644 --- a/packages/app-bridge/src/react/useDocumentPageTargets.spec.ts +++ b/packages/app-bridge/src/react/useDocumentPageTargets.spec.ts @@ -5,7 +5,6 @@ import mitt from 'mitt'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { DocumentPageTargetsDummy } from '../tests'; import { useDocumentPageTargets } from './useDocumentPageTargets'; @@ -13,7 +12,7 @@ import { useDocumentPageTargets } from './useDocumentPageTargets'; const DOCUMENT_PAGE_ID = 345; describe('useDocumentPageTargets', () => { - const appBridge: AppBridgeBlock | AppBridgeTheme = {} as AppBridgeBlock | AppBridgeTheme; + const appBridge: AppBridgeBlock = {} as AppBridgeBlock; beforeEach(() => { window.emitter = mitt(); diff --git a/packages/app-bridge/src/react/useDocumentPageTargets.ts b/packages/app-bridge/src/react/useDocumentPageTargets.ts index d1d8965f4..ed42101ef 100644 --- a/packages/app-bridge/src/react/useDocumentPageTargets.ts +++ b/packages/app-bridge/src/react/useDocumentPageTargets.ts @@ -3,10 +3,9 @@ import { useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentPageTargets } from '../types'; -export type UseDocumentPageTargetsReturnType = { +type UseDocumentPageTargetsReturnType = { documentPageTargets: Nullable; isLoading: boolean; }; @@ -19,10 +18,7 @@ export type DocumentPageTargetEvent = { }; }; -export const useDocumentPageTargets = ( - appBridge: AppBridgeBlock | AppBridgeTheme, - id: number, -): UseDocumentPageTargetsReturnType => { +export const useDocumentPageTargets = (appBridge: AppBridgeBlock, id: number): UseDocumentPageTargetsReturnType => { const [documentPageTargets, setDocumentPageTargets] = useState>(null); const [isLoading, setIsLoading] = useState(true); diff --git a/packages/app-bridge/src/react/useDocumentSection.spec.ts b/packages/app-bridge/src/react/useDocumentSection.spec.ts index e3f29d5d8..dc3e0185e 100644 --- a/packages/app-bridge/src/react/useDocumentSection.spec.ts +++ b/packages/app-bridge/src/react/useDocumentSection.spec.ts @@ -4,7 +4,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import mitt from 'mitt'; import { type Mock, beforeAll, describe, expect, it, vi } from 'vitest'; -import { type AppBridgeBlock, type AppBridgeTheme } from '../'; +import { type AppBridgeBlock } from '../'; import { DocumentSectionDummy } from '../tests/DocumentSectionDummy'; import { useDocumentSection } from './useDocumentSection'; @@ -12,7 +12,6 @@ import { useDocumentSection } from './useDocumentSection'; const DOCUMENT_PAGE_ID = 45; const documentSections = [ - // @ts-expect-error On purpose to test the function DocumentSectionDummy.withFields({ id: 464, title: null }), DocumentSectionDummy.withFields({ id: 356, title: 'New Title' }), DocumentSectionDummy.withFields({ id: 376, title: ' ' }), @@ -20,7 +19,7 @@ const documentSections = [ ]; describe('useDocumentSection', () => { - const appBridge: AppBridgeBlock | AppBridgeTheme = {} as AppBridgeBlock | AppBridgeTheme; + const appBridge: AppBridgeBlock = {} as AppBridgeBlock; beforeAll(() => { window.emitter = mitt(); diff --git a/packages/app-bridge/src/react/useDocumentSection.ts b/packages/app-bridge/src/react/useDocumentSection.ts index 5fabcf9fc..204945ad6 100644 --- a/packages/app-bridge/src/react/useDocumentSection.ts +++ b/packages/app-bridge/src/react/useDocumentSection.ts @@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { filterDocumentSectionsWithUnreadableTitles } from '../helpers'; import { type DocumentSection, type EmitterEvents } from '../types'; @@ -29,7 +28,7 @@ const insertDocumentSectionIntoArray = ( const updateDocumentSectionInArray = ( previousDocumentSections: DocumentSection[], documentSectionIdToUpdate: number, - title: string, + title: Nullable, slug: string, ) => previousDocumentSections.map((documentSection) => @@ -51,10 +50,7 @@ type UseDocumentSectionReturn = { navigationItems: DocumentSection[]; }; -export const useDocumentSection = ( - appBridge: AppBridgeBlock | AppBridgeTheme, - documentPageId: number, -): UseDocumentSectionReturn => { +export const useDocumentSection = (appBridge: AppBridgeBlock, documentPageId: number): UseDocumentSectionReturn => { const [documentSections, setDocumentSections] = useState([]); const navigationItems = useMemo( () => filterDocumentSectionsWithUnreadableTitles(documentSections), diff --git a/packages/app-bridge/src/react/useDocumentTargets.spec.ts b/packages/app-bridge/src/react/useDocumentTargets.spec.ts index 375a3e734..f71e53a74 100644 --- a/packages/app-bridge/src/react/useDocumentTargets.spec.ts +++ b/packages/app-bridge/src/react/useDocumentTargets.spec.ts @@ -5,7 +5,6 @@ import mitt from 'mitt'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { DocumentTargetsDummy } from '../tests'; import { useDocumentTargets } from './useDocumentTargets'; @@ -13,7 +12,7 @@ import { useDocumentTargets } from './useDocumentTargets'; const DOCUMENT_ID = 92341; describe('useDocumentTargets', () => { - const appBridge: AppBridgeBlock | AppBridgeTheme = {} as AppBridgeBlock | AppBridgeTheme; + const appBridge: AppBridgeBlock = {} as AppBridgeBlock; beforeEach(() => { window.emitter = mitt(); diff --git a/packages/app-bridge/src/react/useDocumentTargets.tsx b/packages/app-bridge/src/react/useDocumentTargets.tsx index ff3665a2d..17ae4a846 100644 --- a/packages/app-bridge/src/react/useDocumentTargets.tsx +++ b/packages/app-bridge/src/react/useDocumentTargets.tsx @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentTargets } from '../types'; export type UseDocumentTargetsReturnType = { @@ -19,10 +18,7 @@ type DocumentTargetEvent = { }; }; -export const useDocumentTargets = ( - appBridge: AppBridgeBlock | AppBridgeTheme, - id: number, -): UseDocumentTargetsReturnType => { +export const useDocumentTargets = (appBridge: AppBridgeBlock, id: number): UseDocumentTargetsReturnType => { const [documentTargets, setDocumentTargets] = useState>(null); const [isLoading, setIsLoading] = useState(true); diff --git a/packages/app-bridge/src/react/useEditorState.ts b/packages/app-bridge/src/react/useEditorState.ts index a83ca4516..074d744af 100644 --- a/packages/app-bridge/src/react/useEditorState.ts +++ b/packages/app-bridge/src/react/useEditorState.ts @@ -3,9 +3,8 @@ import { useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; -export const useEditorState = (appBridge: AppBridgeBlock | AppBridgeTheme): boolean => { +export const useEditorState = (appBridge: AppBridgeBlock): boolean => { const [editorState, setEditorState] = useState(appBridge.getEditorState()); useEffect(() => { diff --git a/packages/app-bridge/src/react/useGroupedDocuments.spec.ts b/packages/app-bridge/src/react/useGroupedDocuments.spec.ts index b4195766a..0c3c5f0cf 100644 --- a/packages/app-bridge/src/react/useGroupedDocuments.spec.ts +++ b/packages/app-bridge/src/react/useGroupedDocuments.spec.ts @@ -5,7 +5,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { DocumentDummy, getAppBridgeThemeStub } from '../tests'; +import { DocumentDummy, getAppBridgeBlockStub } from '../tests'; import { useGroupedDocuments } from './useGroupedDocuments'; @@ -23,7 +23,7 @@ describe('useGroupedDocument', () => { }); it('should fetch grouped documents on mount', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const { result } = renderHook(() => useGroupedDocuments(appBridge, DOCUMENT_GROUP_ID_1)); @@ -44,7 +44,7 @@ describe('useGroupedDocument', () => { }); it('should not fetch grouped documents on mount if not enabled', () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const { result } = renderHook(() => useGroupedDocuments(appBridge, DOCUMENT_GROUP_ID_1, { enabled: false })); @@ -55,7 +55,7 @@ describe('useGroupedDocument', () => { }); it('should fetch grouped documents if it gets enabled', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); let enabled = false; @@ -86,7 +86,7 @@ describe('useGroupedDocument', () => { }); it('should update document if a page is added', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const OLD_DOCUMENT = DocumentDummy.withFields({ @@ -130,7 +130,7 @@ describe('useGroupedDocument', () => { }); it('should update document if a page is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const OLD_DOCUMENT = DocumentDummy.withFields({ @@ -178,7 +178,7 @@ describe('useGroupedDocument', () => { }); it('should update document if a category is added', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const OLD_DOCUMENT = DocumentDummy.withFields({ @@ -223,7 +223,7 @@ describe('useGroupedDocument', () => { }); it('should update document if a category is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const OLD_DOCUMENT = DocumentDummy.withFields({ @@ -271,7 +271,7 @@ describe('useGroupedDocument', () => { }); it('should update document sorting if a document is moved', async () => { - const appBridge = getAppBridgeThemeStub(); + const appBridge = getAppBridgeBlockStub(); const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); const DOCUMENT_1 = DocumentDummy.withFields({ diff --git a/packages/app-bridge/src/react/useGroupedDocuments.ts b/packages/app-bridge/src/react/useGroupedDocuments.ts index 4aedde496..40409852b 100644 --- a/packages/app-bridge/src/react/useGroupedDocuments.ts +++ b/packages/app-bridge/src/react/useGroupedDocuments.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type Document, type EmitterEvents } from '../types'; type DocumentPageEvent = EmitterEvents['AppBridge:GuidelineDocument:DocumentPageAction']; @@ -21,7 +20,7 @@ type Options = { const sortDocuments = (a: Document, b: Document) => (a.sort && b.sort ? a.sort - b.sort : 0); export const useGroupedDocuments = ( - appBridge: AppBridgeBlock | AppBridgeTheme, + appBridge: AppBridgeBlock, documentGroupId: number, options: Options = { enabled: true }, ) => { @@ -229,7 +228,7 @@ const actionHandlers = { default: (documents: Map) => documents, }; -const fetchGroupedDocuments = async (appBridge: AppBridgeBlock | AppBridgeTheme, documentGroupId: number) => { +const fetchGroupedDocuments = async (appBridge: AppBridgeBlock, documentGroupId: number) => { const documents = await appBridge.getDocumentsByDocumentGroupId(documentGroupId); return new Map([...documents].sort(sortDocuments).map((document) => [document.id, document])); }; diff --git a/packages/app-bridge/src/react/useGuidelineActions.spec.ts b/packages/app-bridge/src/react/useGuidelineActions.spec.ts deleted file mode 100644 index 89d02fb50..000000000 --- a/packages/app-bridge/src/react/useGuidelineActions.spec.ts +++ /dev/null @@ -1,2178 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, renderHook, waitFor } from '@testing-library/react'; -import mitt from 'mitt'; -import { type SpyInstance, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { - BrandportalLinkDummy, - CoverPageDummy, - DocumentCategoryDummy, - DocumentDummy, - DocumentGroupDummy, - DocumentPageDummy, - getAppBridgeThemeStub, -} from '../tests'; -import { - type BrandportalLink, - type CoverPage, - type CoverPageCreate, - type CoverPageUpdate, - type DocumentCategoryCreate, - type DocumentCategoryUpdate, - type DocumentGroupCreate, - type DocumentGroupUpdate, - type DocumentLibraryCreate, - type DocumentLibraryUpdate, - type DocumentLinkCreate, - type DocumentLinkUpdate, - type DocumentPageCreate, - type DocumentPageUpdate, - type DocumentStandardCreate, - type DocumentStandardUpdate, -} from '../types'; - -import { useCategorizedDocumentPages } from './useCategorizedDocumentPages'; -import { useDocumentCategories } from './useDocumentCategories'; -import { useDocumentGroups } from './useDocumentGroups'; -import { useGroupedDocuments } from './useGroupedDocuments'; -import { useGuidelineActions } from './useGuidelineActions'; -import { useUncategorizedDocumentPages } from './useUncategorizedDocumentPages'; -import { useUngroupedDocuments } from './useUngroupedDocuments'; - -const DOCUMENT_ID_1 = 6456; -const DOCUMENT_ID_2 = 34532; -const DOCUMENT_ID_3 = 2414; -const DOCUMENT_ID_4 = 2342; -const DOCUMENT_ID_5 = 2343445; -const DOCUMENT_PAGE_ID_1 = 23442; -const DOCUMENT_PAGE_ID_2 = 235345; -const DOCUMENT_PAGE_ID_3 = 12352; -const DOCUMENT_PAGE_ID_4 = 55221; -const DOCUMENT_PAGE_ID_5 = 98324; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_1 = 24324; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_2 = 3532; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_3 = 98954; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_4 = 34563; -const DOCUMENT_CATEGORY_ID_1 = 147; -const DOCUMENT_CATEGORY_ID_2 = 258; -const DOCUMENT_CATEGORY_ID_3 = 678; -const DOCUMENT_GROUP_ID_1 = 345; -const DOCUMENT_GROUP_ID_2 = 95694; -const DOCUMENT_GROUP_ID_3 = 345882; - -describe('useGuidelineActions hook', () => { - let useGuidelineActionsStub: ReturnType; - let emitSpy: SpyInstance | null = null; - let appBridgeStub: ReturnType; - - beforeEach(() => { - appBridgeStub = getAppBridgeThemeStub(); - const { result } = renderHook(() => useGuidelineActions(appBridgeStub)); - - useGuidelineActionsStub = result.current; - window.emitter = mitt(); - emitSpy = vi.spyOn(window.emitter, 'emit'); - }); - - afterEach(() => { - vi.resetAllMocks(); - emitSpy = null; - }); - - it('should not throw createDocument', async () => { - const createDocumentMock = vi.fn(useGuidelineActionsStub.createDocument); - - expect(createDocumentMock({ title: 'testDocument' })).resolves.not.toThrow(); - }); - - it('should not throw createDocumentGroup', async () => { - const createDocumentGroupMock = vi.fn(useGuidelineActionsStub.createDocumentGroup); - - expect(createDocumentGroupMock({ name: 'testDocumentGroup' })).resolves.not.toThrow(); - }); - - it('should not throw createLibrary', async () => { - const createLibraryMock = vi.fn(useGuidelineActionsStub.createLibrary); - - expect( - createLibraryMock({ title: 'testLibrary', settings: { project: 4 }, mode: 'DOCUMENTLIBRARY' }), - ).resolves.not.toThrow(); - }); - - it('should not throw createLink', async () => { - const createLinkMock = vi.fn(useGuidelineActionsStub.createLink); - - expect( - createLinkMock({ title: 'testLink', linkUrl: '/test/url', linkSettings: { newTab: true } }), - ).resolves.not.toThrow(); - }); - - it('should not throw createDocumentCategory', async () => { - const createDocumentCategoryMock = vi.fn(useGuidelineActionsStub.createDocumentCategory); - - expect(createDocumentCategoryMock({ title: 'testCategory', documentId: 4 })).resolves.not.toThrow(); - }); - - it('should not throw createDocumentPage', async () => { - const createDocumentPageMock = vi.fn(useGuidelineActionsStub.createDocumentPage); - - expect(createDocumentPageMock({ title: 'testPage', documentId: 5 })).resolves.not.toThrow(); - }); - - it('should not throw createCoverPage', async () => { - const createCoverPageMock = vi.fn(useGuidelineActionsStub.createCoverPage); - - expect(createCoverPageMock({ documentId: '5', template: 'cover' })).resolves.not.toThrow(); - }); - - it('should not throw updateDocument', async () => { - const updateDocumentMock = vi.fn(useGuidelineActionsStub.updateDocument); - - expect(updateDocumentMock({ id: 1, title: 'updateDocumentTest' })).resolves.not.toThrow(); - }); - - it('should not throw updateDocumentGroup', async () => { - const updateDocumentGroupMock = vi.fn(useGuidelineActionsStub.updateDocumentGroup); - - expect(updateDocumentGroupMock({ id: 1, name: 'updateDocumentGroupTest' })).resolves.not.toThrow(); - }); - - it('should not throw updateLibrary', async () => { - const updateLibraryMock = vi.fn(useGuidelineActionsStub.updateLibrary); - - expect(updateLibraryMock({ id: 1, title: 'updateTitleTest' })).resolves.not.toThrow(); - }); - - it('should not throw updateLink', async () => { - const updateLinkMock = vi.fn(useGuidelineActionsStub.updateLink); - - expect(updateLinkMock({ id: 1, title: 'updateLinkTest' })).resolves.not.toThrow(); - }); - - it('should not throw updateDocumentCategory', async () => { - const updateDocumentCategoryMock = vi.fn(useGuidelineActionsStub.updateDocumentCategory); - - expect( - updateDocumentCategoryMock({ id: 1, title: 'updateDocumentCategoryTest', documentId: 20 }), - ).resolves.not.toThrow(); - }); - - it('should not throw updateDocumentPage', async () => { - const updateDocumentPageMock = vi.fn(useGuidelineActionsStub.updateDocumentPage); - - expect( - updateDocumentPageMock({ id: 1, title: 'updateDocumentPageTest', documentId: 20 }), - ).resolves.not.toThrow(); - }); - - it('should not throw updateLegacyCoverPage', async () => { - const updateLegacyCoverPageMock = vi.fn(useGuidelineActionsStub.updateLegacyCoverPage); - - expect(updateLegacyCoverPageMock({})).resolves.not.toThrow(); - }); - - it('should not throw updateCoverPage', async () => { - const updateCoverPageMock = vi.fn(useGuidelineActionsStub.updateCoverPage); - - expect(updateCoverPageMock({ id: 1 })).resolves.not.toThrow(); - }); - - it('should not throw updateBrandportalLink', async () => { - const updateBrandportalLinkMock = vi.fn(useGuidelineActionsStub.updateBrandportalLink); - - expect(updateBrandportalLinkMock({})).resolves.not.toThrow(); - }); - - it('should not throw deleteDocument', async () => { - const deleteDocumentMock = vi.fn(useGuidelineActionsStub.deleteDocument); - - expect(deleteDocumentMock({ id: 1 })).resolves.not.toThrow(); - }); - - it('should not throw deleteLibrary', async () => { - const deleteLibraryMock = vi.fn(useGuidelineActionsStub.deleteLibrary); - - expect(deleteLibraryMock({ id: 1 })).resolves.not.toThrow(); - }); - - it('should not throw deleteLink', async () => { - const deleteLinkMock = vi.fn(useGuidelineActionsStub.deleteLink); - - expect(deleteLinkMock({ id: 1 })).resolves.not.toThrow(); - }); - - it('should not throw deleteDocumentPage', async () => { - const deleteDocumentPageMock = vi.fn(useGuidelineActionsStub.deleteDocumentPage); - - expect(deleteDocumentPageMock({ id: 1, documentId: 10, categoryId: null })).resolves.not.toThrow(); - }); - - it('should not throw deleteDocumentGroup', async () => { - const deleteDocumentGroupMock = vi.fn(useGuidelineActionsStub.deleteDocumentGroup); - - expect(deleteDocumentGroupMock({ id: 1 })).resolves.not.toThrow(); - }); - - it('should not throw deleteDocumentCategory', async () => { - const deleteDocumentCategoryMock = vi.fn(useGuidelineActionsStub.deleteDocumentCategory); - - expect(deleteDocumentCategoryMock({ id: 1, documentId: 10 })).resolves.not.toThrow(); - }); - - it('should not throw deleteCoverPage', async () => { - const deleteCoverPageMock = vi.fn(useGuidelineActionsStub.deleteCoverPage); - - expect(deleteCoverPageMock()).resolves.not.toThrow(); - }); - - it('should not throw duplicateDocumentPage', async () => { - const duplicateDocumentPageMock = vi.fn(useGuidelineActionsStub.duplicateDocumentPage); - - expect(duplicateDocumentPageMock({ id: 1, documentId: 5 })).resolves.not.toThrow(); - }); - - it('should not throw updateDocumentTargets', async () => { - const updateDocumentTargetsMock = vi.fn(useGuidelineActionsStub.updateDocumentTargets); - - expect(updateDocumentTargetsMock([1, 2, 3], [203])).resolves.not.toThrow(); - }); - - it('should not throw updateDocumentPageTargets', async () => { - const updateDocumentPageTargetsMock = vi.fn(useGuidelineActionsStub.updateDocumentPageTargets); - - expect(updateDocumentPageTargetsMock([1, 2, 3], [203])).resolves.not.toThrow(); - }); - - it('should create a link and emit an event', async () => { - const createLink = vi.spyOn(useGuidelineActionsStub, 'createLink'); - - const link: DocumentLinkCreate = { - title: 'Test Link', - documentGroupId: 1, - linkUrl: 'test/url', - linkSettings: { newTab: false }, - }; - - act(() => { - useGuidelineActionsStub.createLink(link); - }); - - await waitFor(() => { - expect(createLink).toHaveBeenCalledWith(link); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: link.documentGroupId }, - action: 'add', - }); - }); - }); - - it('should update a link and emit an event', async () => { - const updateLink = vi.spyOn(useGuidelineActionsStub, 'updateLink'); - - const link: DocumentLinkUpdate = { - id: 1, - title: 'Updated Link', - documentGroupId: 1, - }; - - act(() => { - useGuidelineActionsStub.updateLink(link); - }); - - await waitFor(() => { - expect(updateLink).toHaveBeenCalledWith(link); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: link.documentGroupId }, - action: 'update', - }); - }); - }); - - it('should delete a link and emit an event', async () => { - const deleteLink = vi.spyOn(useGuidelineActionsStub, 'deleteLink'); - - const documentId = 1; - - act(() => { - useGuidelineActionsStub.deleteLink({ id: documentId }); - }); - - await waitFor(() => { - expect(deleteLink).toHaveBeenCalledWith({ id: documentId }); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { id: documentId }, - action: 'delete', - }); - }); - }); - - it('should create a library and emit an event', async () => { - const createLibrary = vi.spyOn(useGuidelineActionsStub, 'createLibrary'); - - const library: DocumentLibraryCreate = { - title: 'Test Library', - documentGroupId: 1, - mode: 'DOCUMENTLIBRARY', - settings: { - project: 1, - }, - }; - - act(() => { - useGuidelineActionsStub.createLibrary(library); - }); - - await waitFor(() => { - expect(createLibrary).toHaveBeenCalledWith(library); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: library.documentGroupId }, - action: 'add', - }); - }); - }); - - it('should update a library and emit an event', async () => { - const updateLibrary = vi.spyOn(useGuidelineActionsStub, 'updateLibrary'); - - const library: DocumentLibraryUpdate = { - id: 1, - title: 'Updated Library', - documentGroupId: 1, - }; - - act(() => { - useGuidelineActionsStub.updateLibrary(library); - }); - - await waitFor(() => { - expect(updateLibrary).toHaveBeenCalledWith(library); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: library.documentGroupId }, - action: 'update', - }); - }); - }); - - it('should delete a library and emit an event', async () => { - const deleteLibrary = vi.spyOn(useGuidelineActionsStub, 'deleteLibrary'); - - const documentId = 1; - - act(() => { - useGuidelineActionsStub.deleteLibrary({ id: documentId }); - }); - - await waitFor(() => { - expect(deleteLibrary).toHaveBeenCalledWith({ id: documentId }); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { id: documentId }, - action: 'delete', - }); - }); - }); - - it('should create a standard document and emit an event', async () => { - const createStandardDocument = vi.spyOn(useGuidelineActionsStub, 'createDocument'); - - const documentCreationParameters: DocumentStandardCreate = { - title: 'Test Standard Document', - documentGroupId: 1, - }; - - act(() => { - useGuidelineActionsStub.createDocument(documentCreationParameters); - }); - - await waitFor(() => { - expect(createStandardDocument).toHaveBeenCalledWith(documentCreationParameters); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: documentCreationParameters.documentGroupId }, - action: 'add', - }); - }); - }); - - it('should update a standard document and emit an event', async () => { - const updateStandardDocument = vi.spyOn(useGuidelineActionsStub, 'updateDocument'); - - const document: DocumentStandardUpdate = { - id: 1, - title: 'Updated Standard Document', - documentGroupId: 1, - }; - - act(() => { - useGuidelineActionsStub.updateDocument(document); - }); - - await waitFor(() => { - expect(updateStandardDocument).toHaveBeenCalledWith(document); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { ...DocumentDummy.with(1), documentGroupId: document.documentGroupId }, - action: 'update', - }); - }); - }); - - it('should delete a standard document and emit an event', async () => { - const deleteStandardDocument = vi.spyOn(useGuidelineActionsStub, 'deleteDocument'); - - const documentId = 1; - - act(() => { - useGuidelineActionsStub.deleteDocument({ id: documentId }); - }); - - await waitFor(() => { - expect(deleteStandardDocument).toHaveBeenCalledWith({ id: documentId }); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocument:Action', { - document: { id: documentId }, - action: 'delete', - }); - }); - }); - - it('should create a cover page and emit an event', async () => { - const createCoverPage = vi.spyOn(useGuidelineActionsStub, 'createCoverPage'); - - const coverPage: CoverPageCreate = { - template: 'blank', - documentId: '1', - }; - - act(() => { - useGuidelineActionsStub.createCoverPage(coverPage); - }); - - await waitFor(() => { - expect(createCoverPage).toHaveBeenCalledWith(coverPage); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineCoverPage:Action', { - coverPage: CoverPageDummy.with(1), - action: 'add', - }); - }); - }); - - it('should update a cover page and emit an event', async () => { - const updateCoverPage = vi.spyOn(useGuidelineActionsStub, 'updateCoverPage'); - - const coverPage: CoverPageUpdate = { - id: 1, - draft: false, - enabled: true, - }; - - act(() => { - useGuidelineActionsStub.updateCoverPage(coverPage); - }); - - await waitFor(() => { - expect(updateCoverPage).toHaveBeenCalledWith(coverPage); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineCoverPage:Action', { - coverPage: CoverPageDummy.with(1), - action: 'update', - }); - }); - }); - - it('should update a legacy cover page and emit an event', async () => { - const updateLegacyCoverPage = vi.spyOn(useGuidelineActionsStub, 'updateLegacyCoverPage'); - - const legacyCoverPage: Partial = { - draft: false, - enabled: true, - title: 'Legacy Cover Page', - }; - - act(() => { - useGuidelineActionsStub.updateLegacyCoverPage(legacyCoverPage); - }); - - await waitFor(() => { - expect(updateLegacyCoverPage).toHaveBeenCalledWith(legacyCoverPage); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineCoverPage:Action', { - coverPage: legacyCoverPage, - action: 'update', - }); - }); - }); - - it('should delete a cover page and emit an event', async () => { - const deleteCoverPage = vi.spyOn(useGuidelineActionsStub, 'deleteCoverPage'); - - act(() => { - useGuidelineActionsStub.deleteCoverPage(); - }); - - await waitFor(() => { - expect(deleteCoverPage).toHaveBeenCalled(); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineCoverPage:Action', { - action: 'delete', - }); - }); - }); - - it('should update a brandportal link and emit an event', async () => { - const updateBrandportalLink = vi.spyOn(useGuidelineActionsStub, 'updateBrandportalLink'); - - const brandportalLink: BrandportalLink = { - label: 'Updated BrandportalLink', - url: 'https://www.example.com', - enabled: true, - }; - - act(() => { - useGuidelineActionsStub.updateBrandportalLink(brandportalLink); - }); - - await waitFor(() => { - expect(updateBrandportalLink).toHaveBeenCalledWith(brandportalLink); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineBrandportalLink:Action', { - brandportalLink: BrandportalLinkDummy.with(), - action: 'update', - }); - }); - }); - - it('should create a document group and emit an event', async () => { - const createDocumentGroup = vi.spyOn(useGuidelineActionsStub, 'createDocumentGroup'); - - const group: DocumentGroupCreate = { - name: 'Test Document Group', - }; - - act(() => { - useGuidelineActionsStub.createDocumentGroup(group); - }); - - await waitFor(() => { - expect(createDocumentGroup).toHaveBeenCalledWith(group); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: DocumentGroupDummy.with(1), - action: 'add', - }); - }); - }); - - it('should update a document group and emit an event', async () => { - const updateDocumentGroup = vi.spyOn(useGuidelineActionsStub, 'updateDocumentGroup'); - - const documentGroupUpdate: DocumentGroupUpdate = { - id: 1, - name: 'Updated Document Group', - }; - - const documentGroup = DocumentGroupDummy.with(1); - - act(() => { - useGuidelineActionsStub.updateDocumentGroup(documentGroupUpdate); - }); - - await waitFor(() => { - expect(updateDocumentGroup).toHaveBeenCalledWith(documentGroupUpdate); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup, - action: 'update', - }); - }); - }); - - it('should delete a document group and emit an event', async () => { - const deleteDocumentGroup = vi.spyOn(useGuidelineActionsStub, 'deleteDocumentGroup'); - - const id = 1; - - act(() => { - useGuidelineActionsStub.deleteDocumentGroup({ id }); - }); - - await waitFor(() => { - expect(deleteDocumentGroup).toHaveBeenCalledWith({ id }); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: { id }, - action: 'delete', - }); - }); - }); - - it('should create a document category and emit an event', async () => { - const createDocumentCategory = vi.spyOn(useGuidelineActionsStub, 'createDocumentCategory'); - - const documentCategoryCreationParameters: DocumentCategoryCreate = { - title: 'Test Document Category', - documentId: 1, - }; - - act(() => { - useGuidelineActionsStub.createDocumentCategory(documentCategoryCreationParameters); - }); - - await waitFor(() => { - expect(createDocumentCategory).toHaveBeenCalledWith(documentCategoryCreationParameters); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory: DocumentCategoryDummy.with(1), - action: 'add', - }); - }); - }); - - it('should update a document category and emit an event', async () => { - const updateDocumentCategory = vi.spyOn(useGuidelineActionsStub, 'updateDocumentCategory'); - - const updatedDocumentCategory: DocumentCategoryUpdate = { - id: 1, - title: 'Updated Document Category', - documentId: 15, - }; - - const documentCategory = DocumentCategoryDummy.with(1); - - act(() => { - useGuidelineActionsStub.updateDocumentCategory(updatedDocumentCategory); - }); - - await waitFor(() => { - expect(updateDocumentCategory).toHaveBeenCalledWith(updatedDocumentCategory); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory, - action: 'update', - }); - }); - }); - - it('should delete a document category and emit an event', async () => { - const deleteDocumentCategory = vi.spyOn(useGuidelineActionsStub, 'deleteDocumentCategory'); - - const documentCategory = { id: 1, documentId: 16 }; - - act(() => { - useGuidelineActionsStub.deleteDocumentCategory(documentCategory); - }); - - await waitFor(() => { - expect(deleteDocumentCategory).toHaveBeenCalledWith(documentCategory); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory: { id: documentCategory.id, documentId: 16 }, - action: 'delete', - }); - }); - }); - - it('should create a page and emit an event', async () => { - const createDocumentPage = vi.spyOn(useGuidelineActionsStub, 'createDocumentPage'); - - const page: DocumentPageCreate = { - title: 'Test Page', - documentId: 1, - }; - - act(() => { - useGuidelineActionsStub.createDocumentPage(page); - }); - - await waitFor(() => { - expect(createDocumentPage).toHaveBeenCalledWith(page); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentPage:Action', { - documentPage: DocumentPageDummy.with(1), - action: 'add', - }); - }); - }); - - it('should update a page and emit an event', async () => { - const documentPage = DocumentPageDummy.with(1); - const updateDocumentPage = vi.spyOn(useGuidelineActionsStub, 'updateDocumentPage'); - - const page: DocumentPageUpdate = { - id: 1, - title: 'Updated Page', - documentId: 25, - }; - - act(() => { - useGuidelineActionsStub.updateDocumentPage(page); - }); - - await waitFor(() => { - expect(updateDocumentPage).toHaveBeenCalledWith(page); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentPage:Action', { - action: 'update', - documentPage, - }); - }); - }); - - it('should delete a page and emit an event', async () => { - const deleteDocumentPage = vi.spyOn(useGuidelineActionsStub, 'deleteDocumentPage'); - - const page = { id: 1, documentId: 22 }; - - act(() => { - useGuidelineActionsStub.deleteDocumentPage(page); - }); - - await waitFor(() => { - expect(deleteDocumentPage).toHaveBeenCalledWith(page); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentPage:Action', { - documentPage: { id: page.id, documentId: 22, categoryId: null }, - action: 'delete', - }); - }); - }); - - it('should duplicate a page and emit an event', async () => { - const duplicateDocumentPage = vi.spyOn(useGuidelineActionsStub, 'duplicateDocumentPage'); - - const originalDocumentPage = { - id: 2341, - documentId: 10, - categoryId: 1138, - }; - - act(() => { - useGuidelineActionsStub.duplicateDocumentPage(originalDocumentPage); - }); - - await waitFor(() => { - expect(duplicateDocumentPage).toHaveBeenCalledWith(originalDocumentPage); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentPage:Action', { - documentPage: { - ...DocumentPageDummy.with(originalDocumentPage.id), - documentId: originalDocumentPage.documentId, - categoryId: originalDocumentPage.categoryId, - }, - action: 'add', - }); - }); - }); - - it('should update document targets and emit all events', async () => { - const updateDocumentTargets = vi.spyOn(useGuidelineActionsStub, 'updateDocumentTargets'); - - const targets = [1, 2, 3]; - const documentIds = [204]; - - act(() => { - useGuidelineActionsStub.updateDocumentTargets(targets, documentIds); - }); - - await waitFor(() => { - expect(updateDocumentTargets).toHaveBeenCalledWith(targets, documentIds); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentTargets:Action', { - payload: { - targets, - documentIds, - }, - action: 'update', - }); - }); - }); - - it('should update document page targets and emit all events', async () => { - const updateDocumentPageTargets = vi.spyOn(useGuidelineActionsStub, 'updateDocumentPageTargets'); - - const targets = [1, 2, 3]; - const documentPageIds = [204]; - - act(() => { - useGuidelineActionsStub.updateDocumentPageTargets(targets, documentPageIds); - }); - - await waitFor(() => { - expect(updateDocumentPageTargets).toHaveBeenCalledWith(targets, documentPageIds); - expect(emitSpy).toHaveBeenCalledWith('AppBridge:GuidelineDocumentPageTargets:Action', { - payload: { targets, pageIds: documentPageIds }, - action: 'update', - }); - }); - }); - - it('should move an uncategorized document page within the same document', async () => { - const DOCUMENT_PAGE_1 = DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }); - - const appBridge = getAppBridgeThemeStub(); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - const moveDocumentPageSpy = vi - .spyOn(appBridge, 'moveDocumentPage') - .mockResolvedValueOnce({ ...DOCUMENT_PAGE_1, sort: 2 }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const { result: uncategorizedDocumentPages } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_1), - ); - - await waitFor(() => { - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_1.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - guidelineActions.current.moveDocumentPage(DOCUMENT_PAGE_1, DOCUMENT_ID_1, 2); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...DOCUMENT_PAGE_1, - sort: 1, - categoryId: null, - }, - categoryId: null, - documentId: DOCUMENT_ID_1, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 2 }, action: 'move' }, - ], - ['AppBridge:GuidelineDocument:DocumentPageAction', { documentPage: DOCUMENT_PAGE_1, action: 'delete' }], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 2 }, action: 'add' }, - ], - ]); - - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_1.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - }); - - it('should move a categorized document page within the same document category', async () => { - const DOCUMENT_PAGE_1 = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }); - - const appBridge = getAppBridgeThemeStub(); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - const moveDocumentPageSpy = vi - .spyOn(appBridge, 'moveDocumentPage') - .mockResolvedValueOnce({ ...DOCUMENT_PAGE_1, sort: 3 }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const { result: categorizedDocumentPages } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - - await waitFor(() => { - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_1.id, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - }); - - guidelineActions.current.moveDocumentPage(DOCUMENT_PAGE_1, DOCUMENT_ID_1, 3, DOCUMENT_CATEGORY_ID_1); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...DOCUMENT_PAGE_1, - sort: 1, - }, - categoryId: DOCUMENT_CATEGORY_ID_1, - documentId: DOCUMENT_ID_1, - position: 3, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 3 }, action: 'move' }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { documentPage: DOCUMENT_PAGE_1, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 3 }, action: 'add' }, - ], - ]); - - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_1.id, - DOCUMENT_PAGE_ID_4, - ]); - }); - }); - - it('should move a categorized document page to uncategorized within the same document', async () => { - const DOCUMENT_PAGE_1 = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchUncategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi - .spyOn(appBridge, 'moveDocumentPage') - .mockResolvedValueOnce({ ...DOCUMENT_PAGE_1, sort: 3, categoryId: null }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const { result: categorizedDocumentPages } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - const { result: uncategorizedDocumentPages } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_1), - ); - - await waitFor(() => { - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_1.id, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - // Mock the response of the add in uncategorized hook call - fetchUncategorizedDocumentPagesSpy.mockResolvedValue([ - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 2, - }), - { ...DOCUMENT_PAGE_1, sort: 3, categoryId: null }, - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 4, - }), - ]); - - guidelineActions.current.moveDocumentPage(DOCUMENT_PAGE_1, DOCUMENT_ID_1, 2, null); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchUncategorizedDocumentPagesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...DOCUMENT_PAGE_1, - sort: 1, - }, - categoryId: null, - documentId: DOCUMENT_ID_1, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 1 }, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 3, categoryId: null }, action: 'add' }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { documentPage: DOCUMENT_PAGE_1, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { documentPage: { ...DOCUMENT_PAGE_1, sort: 3, categoryId: null }, action: 'add' }, - ], - ]); - - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_1.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - }); - - it('should move an uncategorized document page to a document category within the same document', async () => { - const UNCATEGORIZED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchCategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi - .spyOn(appBridge, 'moveDocumentPage') - .mockResolvedValueOnce({ ...UNCATEGORIZED_DOCUMENT_PAGE, sort: 2, categoryId: DOCUMENT_CATEGORY_ID_1 }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const { result: categorizedDocumentPages } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - const { result: uncategorizedDocumentPages } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_1), - ); - - await waitFor(() => { - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - }); - - // Mock the response of the add in uncategorized hook call - fetchCategorizedDocumentPagesSpy.mockResolvedValue([ - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_1, categoryId: DOCUMENT_CATEGORY_ID_1, sort: 1 }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_2, categoryId: DOCUMENT_CATEGORY_ID_1, sort: 2 }), - { ...UNCATEGORIZED_DOCUMENT_PAGE, sort: 3, categoryId: DOCUMENT_CATEGORY_ID_1 }, - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_3, categoryId: DOCUMENT_CATEGORY_ID_1, sort: 4 }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_4, categoryId: DOCUMENT_CATEGORY_ID_1, sort: 5 }), - ]); - - guidelineActions.current.moveDocumentPage( - UNCATEGORIZED_DOCUMENT_PAGE, - DOCUMENT_ID_1, - 2, - DOCUMENT_CATEGORY_ID_1, - ); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchCategorizedDocumentPagesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }, - categoryId: DOCUMENT_CATEGORY_ID_1, - documentId: DOCUMENT_ID_1, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { documentPage: { ...UNCATEGORIZED_DOCUMENT_PAGE, sort: 1, categoryId: null }, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { ...UNCATEGORIZED_DOCUMENT_PAGE, sort: 2, categoryId: DOCUMENT_CATEGORY_ID_1 }, - action: 'add', - }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { documentPage: UNCATEGORIZED_DOCUMENT_PAGE, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { - documentPage: { ...UNCATEGORIZED_DOCUMENT_PAGE, sort: 2, categoryId: DOCUMENT_CATEGORY_ID_1 }, - action: 'add', - }, - ], - ]); - - expect(uncategorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - - expect(categorizedDocumentPages.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE.id, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - }); - }); - - it('should move an uncategorized document page to another document as uncategorized', async () => { - const UNCATEGORIZED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 1, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchUncategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi.spyOn(appBridge, 'moveDocumentPage').mockResolvedValueOnce({ - ...UNCATEGORIZED_DOCUMENT_PAGE, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - // Mock the response to add an extra category to the document - fetchUncategorizedDocumentPagesSpy.mockResolvedValueOnce([ - UNCATEGORIZED_DOCUMENT_PAGE, - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 3, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 4, - }), - ]); - - const { result: uncategorizedDocumentPagesInDocumentId1 } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_1), - ); - - await waitFor(() => { - expect( - uncategorizedDocumentPagesInDocumentId1.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - const { result: uncategorizedDocumentPagesInDocumentId2 } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_2), - ); - - await waitFor(() => { - expect( - uncategorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - // Mock the response of the add in uncategorized hook call - fetchUncategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }), - { ...UNCATEGORIZED_DOCUMENT_PAGE, documentId: DOCUMENT_ID_2, categoryId: null, sort: 3 }, - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 4, - }), - ]); - - guidelineActions.current.moveDocumentPage(UNCATEGORIZED_DOCUMENT_PAGE, DOCUMENT_ID_2, 2, null); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchUncategorizedDocumentPagesSpy).toHaveBeenCalledTimes(2); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }, - categoryId: null, - documentId: DOCUMENT_ID_2, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: null, - }, - action: 'add', - }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { documentPage: { ...UNCATEGORIZED_DOCUMENT_PAGE, documentId: DOCUMENT_ID_1 }, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: null, - }, - action: 'add', - }, - ], - ]); - - expect( - uncategorizedDocumentPagesInDocumentId1.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - - expect( - uncategorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - }); - - it('should move a categorized document page to another document as uncategorized', async () => { - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_5, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 5, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchCategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); - const fetchUncategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi.spyOn(appBridge, 'moveDocumentPage').mockResolvedValueOnce({ - ...DOCUMENT_PAGE, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - // Mock the response to add an extra category to the document category - fetchCategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 3, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 4, - }), - DOCUMENT_PAGE, - ]); - - const { result: categorizedDocumentPagesInDocumentCategoryId1 } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - - await waitFor(() => { - expect( - categorizedDocumentPagesInDocumentCategoryId1.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - DOCUMENT_PAGE.id, - ]); - }); - - const { result: uncategorizedDocumentPagesInDocumentId2 } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_2), - ); - - await waitFor(() => { - expect( - uncategorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - // Mock the response of the add in uncategorized hook call - fetchUncategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }), - { ...DOCUMENT_PAGE, documentId: DOCUMENT_ID_2, categoryId: null, sort: 3 }, - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 4, - }), - ]); - - guidelineActions.current.moveDocumentPage(DOCUMENT_PAGE, DOCUMENT_ID_2, 2, null); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchUncategorizedDocumentPagesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 5, - documentId: DOCUMENT_ID_1, - }, - categoryId: null, - documentId: DOCUMENT_ID_2, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 5, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: null, - }, - action: 'add', - }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { - documentPage: { - ...DOCUMENT_PAGE, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: null, - }, - action: 'add', - }, - ], - ]); - - expect( - categorizedDocumentPagesInDocumentCategoryId1.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([DOCUMENT_PAGE_ID_1, DOCUMENT_PAGE_ID_2, DOCUMENT_PAGE_ID_3, DOCUMENT_PAGE_ID_4]); - - expect( - uncategorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - }); - - it('should move an uncategorized document page to another document as categorized', async () => { - const UNCATEGORIZED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_5, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 1, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchUncategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - const fetchCategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi.spyOn(appBridge, 'moveDocumentPage').mockResolvedValueOnce({ - ...UNCATEGORIZED_DOCUMENT_PAGE, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - // Mock the response to add an extra category to the document - fetchUncategorizedDocumentPagesSpy.mockResolvedValueOnce([ - UNCATEGORIZED_DOCUMENT_PAGE, - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 3, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_1, - categoryId: null, - sort: 4, - }), - ]); - - const { result: uncategorizedDocumentPagesInDocumentId1 } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID_1), - ); - - await waitFor(() => { - expect( - uncategorizedDocumentPagesInDocumentId1.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE.id, - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - }); - - const { result: categorizedDocumentPagesInDocumentId2 } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - - await waitFor(() => { - expect( - categorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([DOCUMENT_PAGE_ID_1, DOCUMENT_PAGE_ID_2, DOCUMENT_PAGE_ID_3, DOCUMENT_PAGE_ID_4]); - }); - - // Mock the response of the add in uncategorized hook call - fetchCategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 2, - }), - { ...UNCATEGORIZED_DOCUMENT_PAGE, documentId: DOCUMENT_ID_2, categoryId: null, sort: 3 }, - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 4, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID_2, - categoryId: null, - sort: 5, - }), - ]); - - guidelineActions.current.moveDocumentPage( - UNCATEGORIZED_DOCUMENT_PAGE, - DOCUMENT_ID_2, - 2, - DOCUMENT_CATEGORY_ID_1, - ); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchUncategorizedDocumentPagesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 1, - documentId: DOCUMENT_ID_1, - }, - categoryId: DOCUMENT_CATEGORY_ID_1, - documentId: DOCUMENT_ID_2, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 1, - documentId: DOCUMENT_ID_1, - categoryId: null, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'add', - }, - ], - [ - 'AppBridge:GuidelineDocument:DocumentPageAction', - { documentPage: { ...UNCATEGORIZED_DOCUMENT_PAGE, documentId: DOCUMENT_ID_1 }, action: 'delete' }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { - documentPage: { - ...UNCATEGORIZED_DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'add', - }, - ], - ]); - - expect( - uncategorizedDocumentPagesInDocumentId1.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - ]); - - expect( - categorizedDocumentPagesInDocumentId2.current.documentPages.map((documentPage) => documentPage.id), - ).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - UNCATEGORIZED_DOCUMENT_PAGE.id, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - }); - }); - - it('should move a categorized document page to another document as categorized', async () => { - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_5, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 5, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchCategorizedDocumentPagesSpy = vi.spyOn(appBridge, 'getDocumentPagesByDocumentCategoryId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentPageSpy = vi.spyOn(appBridge, 'moveDocumentPage').mockResolvedValueOnce({ - ...DOCUMENT_PAGE, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - // Mock the response to add an extra category to the document category - fetchCategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 3, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - sort: 4, - }), - DOCUMENT_PAGE, - ]); - - const { result: categorizedDocumentPagesInDocumentCategoryId1 } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_1), - ); - - await waitFor(() => { - expect( - categorizedDocumentPagesInDocumentCategoryId1.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - DOCUMENT_PAGE.id, - ]); - }); - - const { result: categorizedDocumentPagesInDocumentCategoryId2 } = renderHook(() => - useCategorizedDocumentPages(appBridge, DOCUMENT_CATEGORY_ID_2), - ); - - await waitFor(() => { - expect( - categorizedDocumentPagesInDocumentCategoryId2.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([DOCUMENT_PAGE_ID_1, DOCUMENT_PAGE_ID_2, DOCUMENT_PAGE_ID_3, DOCUMENT_PAGE_ID_4]); - }); - - // Mock the response of the add in uncategorized hook call - fetchCategorizedDocumentPagesSpy.mockResolvedValueOnce([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - sort: 2, - }), - { ...DOCUMENT_PAGE, documentId: DOCUMENT_ID_2, categoryId: DOCUMENT_CATEGORY_ID_2, sort: 3 }, - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - sort: 4, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - sort: 4, - }), - ]); - - guidelineActions.current.moveDocumentPage(DOCUMENT_PAGE, DOCUMENT_ID_2, 2, null); - - expect(moveDocumentPageSpy).toHaveBeenCalledOnce(); - expect(fetchCategorizedDocumentPagesSpy).toHaveBeenCalledTimes(2); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentPage:MoveEvent', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 5, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - categoryId: null, - documentId: DOCUMENT_ID_2, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 5, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocumentPage:Action', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - }, - action: 'add', - }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { - documentPage: { - ...DOCUMENT_PAGE, - documentId: DOCUMENT_ID_1, - categoryId: DOCUMENT_CATEGORY_ID_1, - }, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:DocumentPageAction', - { - documentPage: { - ...DOCUMENT_PAGE, - sort: 2, - documentId: DOCUMENT_ID_2, - categoryId: DOCUMENT_CATEGORY_ID_2, - }, - action: 'add', - }, - ], - ]); - - expect( - categorizedDocumentPagesInDocumentCategoryId1.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([DOCUMENT_PAGE_ID_1, DOCUMENT_PAGE_ID_2, DOCUMENT_PAGE_ID_3, DOCUMENT_PAGE_ID_4]); - - expect( - categorizedDocumentPagesInDocumentCategoryId2.current.documentPages.map( - (documentPage) => documentPage.id, - ), - ).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE.id, - DOCUMENT_PAGE_ID_3, - DOCUMENT_PAGE_ID_4, - ]); - }); - }); - - it('should move an ungrouped document and emit 2 events', async () => { - const DOCUMENT = DocumentDummy.withFields({ ...DocumentDummy.with(DOCUMENT_ID_5), sort: 5 }); - - const appBridge = getAppBridgeThemeStub(); - const fetchDocumentsSpy = vi.spyOn(appBridge, 'getUngroupedDocuments'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentSpy = vi.spyOn(appBridge, 'moveDocument').mockResolvedValueOnce({ - ...DOCUMENT, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - // Mock the response to add an extra category to the document category - fetchDocumentsSpy.mockResolvedValueOnce([ - DocumentDummy.withFields({ ...DocumentDummy.with(DOCUMENT_ID_1), sort: 1 }), - DocumentDummy.withFields({ ...DocumentDummy.with(DOCUMENT_ID_2), sort: 2 }), - DocumentDummy.withFields({ ...DocumentDummy.with(DOCUMENT_ID_3), sort: 3 }), - DocumentDummy.withFields({ ...DocumentDummy.with(DOCUMENT_ID_4), sort: 4 }), - DOCUMENT, - ]); - - const { result: ungroupedDocuments } = renderHook(() => useUngroupedDocuments(appBridge)); - - await waitFor(() => { - expect(ungroupedDocuments.current.documents.map((document) => document.id)).toEqual([ - DOCUMENT_ID_1, - DOCUMENT_ID_2, - DOCUMENT_ID_3, - DOCUMENT_ID_4, - DOCUMENT_ID_5, - ]); - }); - - guidelineActions.current.moveDocument(DOCUMENT, 2); - - expect(moveDocumentSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocument:MoveEvent', - { - document: { - ...DOCUMENT, - sort: 5, - }, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocument:Action', - { - document: { - ...DOCUMENT, - sort: 2, - }, - action: 'move', - }, - ], - ]); - }); - }); - - it('should move a document into a group and emit 3 events', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getDocumentsByDocumentGroupId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const DOCUMENT_1 = DocumentDummy.withFields({ - ...DocumentDummy.with(DOCUMENT_ID_1), - sort: 1, - }); - const DOCUMENT_2 = DocumentDummy.withFields({ - ...DocumentDummy.with(DOCUMENT_ID_2), - sort: 2, - }); - const DOCUMENT_3 = DocumentDummy.withFields({ - ...DocumentDummy.with(DOCUMENT_ID_3), - sort: 3, - }); - const DOCUMENT_4 = DocumentDummy.withFields({ - ...DocumentDummy.with(DOCUMENT_ID_4), - sort: 4, - }); - - const moveGroupSpy = spy.mockResolvedValueOnce([DOCUMENT_1, DOCUMENT_2, DOCUMENT_3, DOCUMENT_4]); - const moveGroupSpyAfter = spy.mockResolvedValueOnce([DOCUMENT_2, DOCUMENT_3, DOCUMENT_4]); - - const { result: groupedDocuments } = renderHook(() => useGroupedDocuments(appBridge, DOCUMENT_GROUP_ID_1)); - - expect(moveGroupSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(groupedDocuments.current.documents.map((document) => document.id)).toEqual([ - DOCUMENT_ID_1, - DOCUMENT_ID_2, - DOCUMENT_ID_3, - DOCUMENT_ID_4, - ]); - }); - - guidelineActions.current.moveDocument(DOCUMENT_1, 5, DOCUMENT_GROUP_ID_2); - - expect(moveGroupSpyAfter).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocument:MoveEvent', - { - document: DOCUMENT_1, - position: 5, - newGroupId: DOCUMENT_GROUP_ID_2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocument:Action', - { - document: DOCUMENT_1, - action: 'delete', - }, - ], - [ - 'AppBridge:GuidelineDocument:Action', - { - document: { - ...DOCUMENT_1, - sort: 5, - }, - action: 'add', - }, - ], - ]); - }); - }); - - it('should move a document group and emit 2 events', async () => { - const DOCUMENT_GROUP_1 = DocumentGroupDummy.withFields({ - ...DocumentGroupDummy.with(DOCUMENT_GROUP_ID_1), - sort: 1, - }); - - const appBridge = getAppBridgeThemeStub(); - const fetchDocumentGroupsSpy = vi.spyOn(appBridge, 'getDocumentGroups'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const moveDocumentGroupSpy = vi.spyOn(appBridge, 'moveDocumentGroup').mockResolvedValueOnce({ - ...DOCUMENT_GROUP_1, - sort: 2, - }); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - fetchDocumentGroupsSpy.mockResolvedValueOnce([ - DOCUMENT_GROUP_1, - DocumentGroupDummy.withFields({ ...DocumentGroupDummy.with(DOCUMENT_GROUP_ID_2), sort: 2 }), - DocumentGroupDummy.withFields({ ...DocumentGroupDummy.with(DOCUMENT_GROUP_ID_3), sort: 3 }), - ]); - - const { result: documentGroups } = renderHook(() => useDocumentGroups(appBridge)); - - await waitFor(() => { - expect(documentGroups.current.documentGroups.map((documentGroup) => documentGroup.id)).toEqual([ - DOCUMENT_GROUP_ID_1, - DOCUMENT_GROUP_ID_2, - DOCUMENT_GROUP_ID_3, - ]); - }); - - guidelineActions.current.moveDocumentGroup(DOCUMENT_GROUP_1, 2); - - expect(moveDocumentGroupSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentGroup:MoveEvent', - { - documentGroup: DOCUMENT_GROUP_1, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentGroup:Action', - { - documentGroup: { - ...DOCUMENT_GROUP_1, - sort: 2, - }, - action: 'update', - }, - ], - ]); - }); - }); - - it('should move a document category and emit 2 events', async () => { - const CATEGORY_1 = DocumentCategoryDummy.withFields({ - ...DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID_1), - sort: 1, - }); - const CATEGORY_2 = DocumentCategoryDummy.withFields({ - ...DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID_2), - sort: 2, - }); - const CATEGORY_3 = DocumentCategoryDummy.withFields({ - ...DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID_3), - sort: 3, - }); - - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getDocumentCategoriesByDocumentId'); - const emitSpy = vi.spyOn(window.emitter, 'emit'); - - const { result: guidelineActions } = renderHook(() => useGuidelineActions(appBridge)); - - const categoriesSpy = spy.mockResolvedValueOnce([CATEGORY_1, CATEGORY_2, CATEGORY_3]); - - const { result: documentCategories } = renderHook(() => useDocumentCategories(appBridge, DOCUMENT_ID_1)); - - expect(categoriesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect( - documentCategories.current.documentCategories.map((documentCategory) => documentCategory.id), - ).toEqual([DOCUMENT_CATEGORY_ID_1, DOCUMENT_CATEGORY_ID_2, DOCUMENT_CATEGORY_ID_3]); - }); - - guidelineActions.current.moveDocumentCategory(CATEGORY_1, DOCUMENT_ID_1, 2); - expect(categoriesSpy).toHaveBeenCalledOnce(); - - await waitFor(() => { - expect(emitSpy.mock.calls).toEqual([ - [ - 'AppBridge:GuidelineDocumentCategory:MoveEvent', - { - documentCategory: CATEGORY_1, - documentId: DOCUMENT_ID_1, - position: 2, - action: 'movePreview', - }, - ], - [ - 'AppBridge:GuidelineDocumentCategory:Action', - { - documentCategory: { - ...CATEGORY_1, - documentId: DOCUMENT_ID_1, - sort: 2, - }, - action: 'update', - }, - ], - ]); - }); - }); -}); diff --git a/packages/app-bridge/src/react/useGuidelineActions.ts b/packages/app-bridge/src/react/useGuidelineActions.ts deleted file mode 100644 index 43aeb604d..000000000 --- a/packages/app-bridge/src/react/useGuidelineActions.ts +++ /dev/null @@ -1,671 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useCallback } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { - type BrandportalLink, - type CoverPage, - type CoverPageCreate, - type CoverPageUpdate, - type CoverPageUpdateLegacy, - type DocumentCategory, - type DocumentCategoryCreate, - type DocumentCategoryDelete, - type DocumentCategoryUpdate, - type DocumentGroup, - type DocumentGroupCreate, - type DocumentGroupDelete, - type DocumentGroupUpdate, - type DocumentLibraryCreate, - type DocumentLibraryDelete, - type DocumentLibraryUpdate, - type DocumentLinkCreate, - type DocumentLinkDelete, - type DocumentLinkUpdate, - type DocumentPage, - type DocumentPageCreate, - type DocumentPageDelete, - type DocumentPageUpdate, - type DocumentStandardCreate, - type DocumentStandardDelete, - type DocumentStandardUpdate, - type EmitterEvents, -} from '../types'; - -export const useGuidelineActions = (appBridge: AppBridgeTheme) => { - const createLink = useCallback( - async (link: DocumentLinkCreate) => { - const result = await appBridge.createLink(link); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { ...result, ...(link.documentGroupId && { documentGroupId: link.documentGroupId }) }, - action: 'add', - }); - - if (link.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { id: result.id, documentGroupId: link.documentGroupId }, - action: 'add', - }); - } - - return result; - }, - [appBridge], - ); - - const updateLink = useCallback( - async (link: DocumentLinkUpdate) => { - const result = await appBridge.updateLink(link); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { ...result, ...(link.documentGroupId && { documentGroupId: link.documentGroupId }) }, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const deleteLink = useCallback( - async (link: DocumentLinkDelete) => { - await appBridge.deleteLink(link.id); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: link, - action: 'delete', - }); - - if (link.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { ...link, documentGroupId: link.documentGroupId }, - action: 'delete', - }); - } - }, - [appBridge], - ); - - const createLibrary = useCallback( - async (library: DocumentLibraryCreate) => { - const result = await appBridge.createLibrary(library); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { - ...result, - ...(library.documentGroupId && { documentGroupId: library.documentGroupId }), - }, - action: 'add', - }); - - if (library.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { id: result.id, documentGroupId: library.documentGroupId }, - action: 'add', - }); - } - - return result; - }, - [appBridge], - ); - - const updateLibrary = useCallback( - async (library: DocumentLibraryUpdate) => { - const result = await appBridge.updateLibrary(library); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { - ...result, - ...(library.documentGroupId && { documentGroupId: library.documentGroupId }), - }, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const deleteLibrary = useCallback( - async (library: DocumentLibraryDelete) => { - await appBridge.deleteLibrary(library.id); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: library, - action: 'delete', - }); - - if (library.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { ...library, documentGroupId: library.documentGroupId }, - action: 'delete', - }); - } - }, - [appBridge], - ); - - const createStandardDocument = useCallback( - async (document: DocumentStandardCreate) => { - const result = await appBridge.createStandardDocument(document); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { - ...result, - ...(document.documentGroupId && { documentGroupId: document.documentGroupId }), - }, - action: 'add', - }); - - if (document.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { id: result.id, documentGroupId: document.documentGroupId }, - action: 'add', - }); - } - - return result; - }, - [appBridge], - ); - - const updateStandardDocument = useCallback( - async (document: DocumentStandardUpdate) => { - const result = await appBridge.updateStandardDocument(document); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { - ...result, - ...(document.documentGroupId && { documentGroupId: document.documentGroupId }), - }, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const deleteStandardDocument = useCallback( - async (document: DocumentStandardDelete) => { - await appBridge.deleteStandardDocument(document.id); - - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document, - action: 'delete', - }); - - if (document.documentGroupId) { - window.emitter.emit('AppBridge:GuidelineDocumentGroup:DocumentAction', { - document: { ...document, documentGroupId: document.documentGroupId }, - action: 'delete', - }); - } - }, - [appBridge], - ); - - const createDocumentGroup = useCallback( - async (documentGroup: DocumentGroupCreate) => { - const result = await appBridge.createDocumentGroup(documentGroup); - - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: result, - action: 'add', - }); - - return result; - }, - [appBridge], - ); - - const updateDocumentGroup = useCallback( - async (documentGroupUpdate: DocumentGroupUpdate) => { - const documentGroup = await appBridge.updateDocumentGroup(documentGroupUpdate); - - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup, - action: 'update', - }); - }, - [appBridge], - ); - - const deleteDocumentGroup = useCallback( - async (documentGroup: DocumentGroupDelete) => { - await appBridge.deleteDocumentGroup(documentGroup.id); - - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup, - action: 'delete', - }); - }, - [appBridge], - ); - - const createDocumentPage = useCallback( - async (documentPage: DocumentPageCreate) => { - const result = await appBridge.createDocumentPage(documentPage); - - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - documentPage: result, - action: 'add', - }); - - if (documentPage.categoryId) { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { - documentPage: { - id: result.id, - categoryId: documentPage.categoryId, - }, - action: 'add', - }); - } - - return result; - }, - [appBridge], - ); - - /** - * A method for page update - * - * @param documentPage - {@link DocumentPageUpdate} object - * @requires id - Indicates page identifier. - * - * - * and at least one of - * - * @property title - Indicates title of a page. - * @property documentId - Indicates to witch document the page belongs to. - * @property categoryId - Indicates to witch category the page belongs to. - * @property visibility - Indicates whether the page is visible only to the editor or everyone. - * @property linkUrl - Indicates whether the page is link or not. - */ - const updateDocumentPage = useCallback( - async (documentPage: DocumentPageUpdate) => { - const result = await appBridge.updateDocumentPage(documentPage); - - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - documentPage: result, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const deleteDocumentPage = useCallback( - async (documentPage: DocumentPageDelete) => { - await appBridge.deleteDocumentPage(documentPage.id); - - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - documentPage: { ...documentPage, categoryId: documentPage.categoryId ?? null }, - action: 'delete', - }); - - if (documentPage.categoryId) { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { - documentPage: { ...documentPage, categoryId: documentPage.categoryId }, - action: 'delete', - }); - } else { - window.emitter.emit('AppBridge:GuidelineDocument:DocumentPageAction', { - documentPage, - action: 'delete', - }); - } - }, - [appBridge], - ); - - const duplicateDocumentPage = useCallback( - async ({ id, documentId, categoryId }: { id: number; documentId: number; categoryId?: number }) => { - const duplicatedDocumentPage = await appBridge.duplicateDocumentPage(id); - - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - documentPage: { ...duplicatedDocumentPage, documentId, categoryId } as unknown as DocumentPage, - action: 'add', - }); - - if (categoryId) { - window.emitter.emit('AppBridge:GuidelineDocumentCategory:DocumentPageAction', { - documentPage: { id: duplicatedDocumentPage.id, categoryId }, - action: 'add', - }); - } - - return duplicatedDocumentPage; - }, - [appBridge], - ); - - const createDocumentCategory = useCallback( - async (category: DocumentCategoryCreate) => { - const result = await appBridge.createDocumentCategory(category); - - window.emitter.emit('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory: result, - action: 'add', - }); - - window.emitter.emit('AppBridge:GuidelineDocument:DocumentCategoryAction', { - documentCategory: result, - action: 'add', - }); - - return result; - }, - [appBridge], - ); - - const updateDocumentCategory = useCallback( - async (category: DocumentCategoryUpdate) => { - const documentCategory = await appBridge.updateDocumentCategory(category); - - window.emitter.emit('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory, - action: 'update', - }); - }, - [appBridge], - ); - - const deleteDocumentCategory = useCallback( - async (documentCategory: DocumentCategoryDelete) => { - await appBridge.deleteDocumentCategory(documentCategory.id); - - window.emitter.emit('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory, - action: 'delete', - }); - - window.emitter.emit('AppBridge:GuidelineDocument:DocumentCategoryAction', { - documentCategory, - action: 'delete', - }); - }, - [appBridge], - ); - - const createCoverPage = useCallback( - async (coverPage: CoverPageCreate) => { - const result = await appBridge.createCoverPage(coverPage); - - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - coverPage: result, - action: 'add', - }); - - return result; - }, - [appBridge], - ); - - const updateCoverPage = useCallback( - async (coverPage: CoverPageUpdate) => { - const result = await appBridge.updateCoverPage(coverPage); - - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - coverPage: result, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - /** - * @deprecated legacy method, should be removed once new endpoint is available - */ - const updateLegacyCoverPage = useCallback( - async (coverPage: Partial) => { - const legacyCoverPage: CoverPageUpdateLegacy = { - ...(coverPage.title && { brandhome_title: coverPage.title }), - ...(coverPage.draft !== undefined && { brandhome_draft: coverPage.draft }), - ...(coverPage.hideInNav !== undefined && { brandhome_hide_in_nav: coverPage.hideInNav }), - }; - - const result = await appBridge.updateLegacyCoverPage(legacyCoverPage); - - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - coverPage: coverPage as CoverPage, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const deleteCoverPage = useCallback(async () => { - await appBridge.deleteCoverPage(); - - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { - action: 'delete', - }); - }, [appBridge]); - - const updateBrandportalLink = useCallback( - async (brandportalLink: Partial) => { - const result = await appBridge.updateBrandportalLink(brandportalLink); - - if (result) { - window.emitter.emit('AppBridge:GuidelineBrandportalLink:Action', { - brandportalLink: result as BrandportalLink, - action: 'update', - }); - } - - return result; - }, - [appBridge], - ); - - const moveDocument = useCallback( - async ( - document: { id: number; sort?: Nullable; documentGroupId?: Nullable }, - position: number, - newGroupId?: number, - ) => { - const previewUpdateChannel: keyof EmitterEvents = 'AppBridge:GuidelineDocument:MoveEvent'; - window.emitter.emit(previewUpdateChannel, { - document, - position, - newGroupId, - action: 'movePreview', - }); - - const result = await appBridge.moveDocument(document.id, position, newGroupId); - - const channel: keyof EmitterEvents = 'AppBridge:GuidelineDocument:Action'; - - // Emits in `useDocumentGroups` hook - if (document.documentGroupId === newGroupId) { - window.emitter.emit(channel, { - document: { ...result, sort: position }, - action: 'move', - }); - } else { - window.emitter.emit(channel, { - document, - action: 'delete', - }); - - window.emitter.emit(channel, { - document: result, - action: 'add', - }); - } - }, - [appBridge], - ); - - const moveDocumentGroup = useCallback( - async (documentGroup: { id: number; sort?: Nullable }, position: number) => { - const previewUpdateChannel: keyof EmitterEvents = 'AppBridge:GuidelineDocumentGroup:MoveEvent'; - window.emitter.emit(previewUpdateChannel, { - documentGroup, - position, - action: 'movePreview', - }); - - await appBridge.moveDocumentGroup(documentGroup.id, position); - - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: { ...documentGroup, sort: position } as DocumentGroup, - action: 'update', - }); - }, - [appBridge], - ); - - const moveDocumentCategory = useCallback( - async (documentCategory: { id: number; sort?: Nullable }, documentId: number, position: number) => { - const previewUpdateChannel: keyof EmitterEvents = 'AppBridge:GuidelineDocumentCategory:MoveEvent'; - window.emitter.emit(previewUpdateChannel, { - documentCategory, - documentId, - position, - action: 'movePreview', - }); - - await appBridge.moveDocumentCategory(documentCategory.id, documentId, position); - - window.emitter.emit('AppBridge:GuidelineDocumentCategory:Action', { - documentCategory: { ...documentCategory, documentId, sort: position } as DocumentCategory, - action: 'update', - }); - }, - [appBridge], - ); - - const moveDocumentPage = useCallback( - async ( - documentPage: { - id: number; - documentId: number; - sort?: Nullable; - categoryId?: Nullable; - }, - documentId: number, - position?: number, - categoryId: Nullable = null, - ) => { - const previewUpdateChannel: keyof EmitterEvents = 'AppBridge:GuidelineDocumentPage:MoveEvent'; - window.emitter.emit(previewUpdateChannel, { - documentPage, - documentId, - position, - categoryId, - action: 'movePreview', - }); - - const result = await appBridge.moveDocumentPage( - documentPage.id, - documentId, - position, - categoryId ?? undefined, - ); - - const channel: keyof EmitterEvents = 'AppBridge:GuidelineDocumentPage:Action'; - - // Emits in `useCategorizedDocumentPages` and `useUncategorizedDocumentPages` hooks - if (documentPage.categoryId === categoryId && documentPage.documentId === documentId) { - window.emitter.emit(channel, { - documentPage: { ...result, sort: position ?? result.sort }, - action: 'move', - }); - } else { - window.emitter.emit(channel, { - documentPage: { ...documentPage, categoryId: documentPage.categoryId ?? null }, - action: 'delete', - }); - - window.emitter.emit(channel, { - documentPage: result, - action: 'add', - }); - } - - // Emits in `useDocumentCategories` and `useDocuments` hook - const deleteChannel = documentPage.categoryId - ? 'AppBridge:GuidelineDocumentCategory:DocumentPageAction' - : 'AppBridge:GuidelineDocument:DocumentPageAction'; - - const addChannel = result.categoryId - ? 'AppBridge:GuidelineDocumentCategory:DocumentPageAction' - : 'AppBridge:GuidelineDocument:DocumentPageAction'; - - window.emitter.emit(deleteChannel, { documentPage, action: 'delete' }); - window.emitter.emit(addChannel, { documentPage: result, action: 'add' }); - }, - [appBridge], - ); - - const updateDocumentPageTargets = useCallback( - async (targets: number[], documentPageIds: number[]) => { - const result = await appBridge.updateDocumentPageTargets(targets, documentPageIds); - - window.emitter.emit('AppBridge:GuidelineDocumentPageTargets:Action', { - payload: { targets, pageIds: documentPageIds }, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - const updateDocumentTargets = useCallback( - async (targets: number[], documentIds: number[]) => { - const result = await appBridge.updateDocumentTargets(targets, documentIds); - - window.emitter.emit('AppBridge:GuidelineDocumentTargets:Action', { - payload: { targets, documentIds }, - action: 'update', - }); - - return result; - }, - [appBridge], - ); - - return { - createLink, - updateLink, - deleteLink, - createDocumentPage, - updateDocumentPage, - deleteDocumentPage, - duplicateDocumentPage, - moveDocumentPage, - createLibrary, - updateLibrary, - deleteLibrary, - createDocumentCategory, - updateDocumentCategory, - deleteDocumentCategory, - moveDocumentCategory, - createCoverPage, - updateCoverPage, - deleteCoverPage, - updateLegacyCoverPage, - moveDocumentGroup, - createDocumentGroup, - updateDocumentGroup, - deleteDocumentGroup, - updateBrandportalLink, - createDocument: createStandardDocument, - updateDocument: updateStandardDocument, - deleteDocument: deleteStandardDocument, - moveDocument, - updateDocumentPageTargets, - updateDocumentTargets, - }; -}; diff --git a/packages/app-bridge/src/react/useLanguage.spec.ts b/packages/app-bridge/src/react/useLanguage.spec.ts deleted file mode 100644 index 5545b11cc..000000000 --- a/packages/app-bridge/src/react/useLanguage.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, renderHook } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import { getAppBridgeThemeStub } from '../tests/AppBridgeThemeStub'; - -import { useLanguage } from './useLanguage'; - -describe('useLanguage', () => { - it('should return default language initially', () => { - const appBridge = getAppBridgeThemeStub(); - appBridge.context.withArgs('currentLanguage').returns({ get: () => 'fr', subscribe: () => vi.fn() }); - appBridge.context.withArgs('defaultLanguage').returns({ get: () => 'en', subscribe: () => vi.fn() }); - - const { result } = renderHook(() => useLanguage(appBridge)); - - expect(result.current.currentLanguage).toEqual('fr'); - expect(result.current.defaultLanguage).toEqual('en'); - }); - - it('should update language on context change', () => { - const appBridge = getAppBridgeThemeStub(); - appBridge.context.withArgs('currentLanguage').returns({ - get: () => 'fr', - subscribe: vi.fn(), - }); - appBridge.context.withArgs('defaultLanguage').returns({ - get: () => 'en', - subscribe: vi.fn(), - }); - - const { result } = renderHook(() => useLanguage(appBridge)); - - expect(result.current.currentLanguage).toEqual('fr'); - expect(result.current.defaultLanguage).toEqual('en'); - - act(() => { - const subscribeFn = appBridge.context('currentLanguage').subscribe; - if (vi.isMockFunction(subscribeFn)) { - subscribeFn.mock.calls[0][0]('de'); - } - }); - - expect(result.current.currentLanguage).toEqual('de'); - expect(result.current.defaultLanguage).toEqual('en'); - }); - - it('should unsubscribe on unmount', () => { - const appBridge = getAppBridgeThemeStub(); - const mockUnsubscribeCurrentLanguage = vi.fn(); - const mockUnsubscribeDefaultLanguage = vi.fn(); - - appBridge.context - .withArgs('currentLanguage') - .returns({ get: () => 'fr', subscribe: () => mockUnsubscribeCurrentLanguage }); - appBridge.context - .withArgs('defaultLanguage') - .returns({ get: () => 'en', subscribe: () => mockUnsubscribeDefaultLanguage }); - - const { unmount } = renderHook(() => useLanguage(appBridge)); - - unmount(); - - expect(mockUnsubscribeCurrentLanguage).toHaveBeenCalledTimes(1); - expect(mockUnsubscribeDefaultLanguage).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/app-bridge/src/react/useLanguage.ts b/packages/app-bridge/src/react/useLanguage.ts deleted file mode 100644 index d0aee479e..000000000 --- a/packages/app-bridge/src/react/useLanguage.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; - -type UseLanguageReturn = { - currentLanguage: string; - defaultLanguage: string; -}; - -export const useLanguage = (appBridge: AppBridgeTheme): UseLanguageReturn => { - const [currentLanguage, setCurrentLanguage] = useState(appBridge.context('currentLanguage').get()); - const [defaultLanguage, setDefaultLanguage] = useState(appBridge.context('defaultLanguage').get()); - - useEffect(() => { - const unsubscribeCurrentLanguageObserver = appBridge.context('currentLanguage').subscribe(setCurrentLanguage); - const unsubscribeDefaultLanguageObserver = appBridge.context('defaultLanguage').subscribe(setDefaultLanguage); - - return () => { - unsubscribeCurrentLanguageObserver(); - unsubscribeDefaultLanguageObserver(); - }; - }, [appBridge]); - - return { currentLanguage, defaultLanguage }; -}; diff --git a/packages/app-bridge/src/react/useNavigationManager.spec.ts b/packages/app-bridge/src/react/useNavigationManager.spec.ts deleted file mode 100644 index 5c3e5494f..000000000 --- a/packages/app-bridge/src/react/useNavigationManager.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { renderHook } from '@testing-library/react'; -import { beforeAll, describe, expect, it, vi } from 'vitest'; - -import { getAppBridgeThemeStub } from '../tests'; - -import { useNavigationManager } from './useNavigationManager'; - -describe('useNavigationManager hook', () => { - let useNavigationManagerStub: ReturnType; - - beforeAll(() => { - const appBridgeStub = getAppBridgeThemeStub(); - const { result } = renderHook(() => useNavigationManager(appBridgeStub)); - - useNavigationManagerStub = result.current; - }); - - it('should call openNavigationManager', async () => { - const spy = vi.spyOn(useNavigationManagerStub, 'openNavigationManager').mockImplementation(() => undefined); - - useNavigationManagerStub.openNavigationManager(); - - expect(spy).toBeCalled(); - }); -}); diff --git a/packages/app-bridge/src/react/useNavigationManager.ts b/packages/app-bridge/src/react/useNavigationManager.ts deleted file mode 100644 index 052b89a43..000000000 --- a/packages/app-bridge/src/react/useNavigationManager.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type AppBridgeTheme } from '../AppBridgeTheme'; - -export const useNavigationManager = (appBridge: AppBridgeTheme) => { - const { openNavigationManager } = appBridge; - - return { - openNavigationManager: () => openNavigationManager(), - }; -}; diff --git a/packages/app-bridge/src/react/usePageTemplateSettings.spec.ts b/packages/app-bridge/src/react/usePageTemplateSettings.spec.ts deleted file mode 100644 index 5c0c6a00f..000000000 --- a/packages/app-bridge/src/react/usePageTemplateSettings.spec.ts +++ /dev/null @@ -1,337 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { cleanup, renderHook, waitFor } from '@testing-library/react'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { getAppBridgeThemeStub } from '../tests'; -import { type ThemeTemplate } from '../types'; - -import { usePageTemplateSettings } from './usePageTemplateSettings'; - -const DOCUMENT_ID = 3462; -const THEME_SETTINGS = { - documentPage: { - customThemeSetting: 123, - themeExclusiveSetting: 234, - }, - cover: {}, - library: {}, -}; -const PAGE_SETTINGS = { - myCustomSetting: 123, -}; - -const PAGE_SETTINGS_WITH_OVERRIDES = { - ...PAGE_SETTINGS, - customThemeSetting: 321, -}; - -const PAGE_SETTINGS_WITH_NULL_OVERRIDES = { - ...PAGE_SETTINGS, - customThemeSetting: null, -}; - -describe('usePageTemplateSettings', () => { - beforeEach(() => { - vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - cleanup(); - }); - - const loadUsePageTemplateSettings = ( - pageTemplateSettings: Record, - template: Parameters[1], - documentId?: Parameters[2], - themeSettings?: Record>, - ) => { - const appBridgeStub = getAppBridgeThemeStub({ - pageTemplateSettings, - themeSettings, - }); - - const { result } = renderHook(() => usePageTemplateSettings(appBridgeStub, template, documentId)); - return { result, appBridgeStub }; - }; - - it('returns the page settings for cover page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'cover'); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - }); - - it('updates the page settings for cover page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'cover'); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - - await result.current.updatePageTemplateSettings({ myCustomSetting: 456 }); - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ ...PAGE_SETTINGS, myCustomSetting: 456 }); - }); - - // Reset the object to avoid mutation in other tests - await result.current.updatePageTemplateSettings({ myCustomSetting: 123 }); - }); - - it('returns the page settings for document page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'documentPage', DOCUMENT_ID); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - }); - - it('returns `null` for document page if no document page id passed', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'documentPage'); - - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(null); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('updates the page settings for document page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'documentPage', DOCUMENT_ID); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - - await result.current.updatePageTemplateSettings({ myCustomSetting: 456 }); - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ ...PAGE_SETTINGS, myCustomSetting: 456 }); - }); - - // Reset the object to avoid mutation in other tests - await result.current.updatePageTemplateSettings({ myCustomSetting: 123 }); - }); - - it('returns the page settings for library page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'library', DOCUMENT_ID); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - }); - - it('returns `null` for library page if no document id passed', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'library'); - - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(null); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('updates the page settings for library page', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'library', DOCUMENT_ID); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - }); - - await result.current.updatePageTemplateSettings({ myCustomSetting: 456 }); - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ ...PAGE_SETTINGS, myCustomSetting: 456 }); - }); - - // Reset the object to avoid mutation in other tests - await result.current.updatePageTemplateSettings({ library: { myCustomSetting: 123 } }); - }); - - it('logs an error when trying to update document or library page without a documentId', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'library'); - - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.pageTemplateSettings).toEqual(null); - }); - - await result.current.updatePageTemplateSettings({ library: { muCystomSetting: 456 } }); - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(null); - }); - - expect(console.error).toHaveBeenCalledTimes(2); - }); - - describe('Theme and Page template overrides', () => { - it('returns the page template settings merged with theme settings and the list of custom overrides', async () => { - const { result } = loadUsePageTemplateSettings( - PAGE_SETTINGS_WITH_OVERRIDES, - 'documentPage', - DOCUMENT_ID, - THEME_SETTINGS, - ); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ - ...THEME_SETTINGS.documentPage, - ...PAGE_SETTINGS_WITH_OVERRIDES, - }); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual(['customThemeSetting']); - expect(result.current.templateThemeSettings).toEqual(THEME_SETTINGS.documentPage); - }); - }); - - it('returns the page template settings merged with theme settings and no overrides', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'cover', undefined, THEME_SETTINGS); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ - ...PAGE_SETTINGS, - ...THEME_SETTINGS.cover, - }); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - }); - - it('returns an empty object if no theme settins and no page template settings', async () => { - const { result } = loadUsePageTemplateSettings({}, 'cover', undefined, { - documentPage: {}, - cover: {}, - library: {}, - }); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({}); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - }); - - it('returns only the theme settings if no page template settings', async () => { - const { result } = loadUsePageTemplateSettings({}, 'cover', undefined, THEME_SETTINGS); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(THEME_SETTINGS.cover); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - }); - - it('returns only the page template settings if no theme settings', async () => { - const { result } = loadUsePageTemplateSettings(PAGE_SETTINGS, 'cover', undefined, { - documentPage: {}, - cover: {}, - library: {}, - }); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual(PAGE_SETTINGS); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - }); - - it('returns theme setting value if page template override is null', async () => { - const { result } = loadUsePageTemplateSettings( - PAGE_SETTINGS_WITH_NULL_OVERRIDES, - 'documentPage', - DOCUMENT_ID, - THEME_SETTINGS, - ); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ - ...PAGE_SETTINGS, - ...THEME_SETTINGS.documentPage, - }); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - }); - - it('returns the theme setting if the override is deleted (reset) from page template settings', async () => { - const { result } = loadUsePageTemplateSettings( - PAGE_SETTINGS_WITH_OVERRIDES, - 'documentPage', - DOCUMENT_ID, - THEME_SETTINGS, - ); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ - ...THEME_SETTINGS.documentPage, - ...PAGE_SETTINGS_WITH_OVERRIDES, - }); - }); - - await result.current.updatePageTemplateSettings({ customThemeSetting: null }); - expect(result.current.isLoading).toEqual(false); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.pageTemplateSettings).toEqual({ - ...PAGE_SETTINGS, - ...THEME_SETTINGS.documentPage, - }); - expect(result.current.customizedPageTemplateSettingsKeys).toEqual([]); - }); - - // Reset the object to avoid mutation in other tests - await result.current.updatePageTemplateSettings({ customThemeSetting: 321 }); - }); - }); -}); diff --git a/packages/app-bridge/src/react/usePageTemplateSettings.ts b/packages/app-bridge/src/react/usePageTemplateSettings.ts deleted file mode 100644 index 37f5ae6e7..000000000 --- a/packages/app-bridge/src/react/usePageTemplateSettings.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type EmitterEvents, type ThemeTemplate } from '../types'; - -import { useThemeSettings } from './'; - -export const usePageTemplateSettings = >( - appBridge: AppBridgeTheme, - template: ThemeTemplate, - documentOrDocumentPageId?: number, -) => { - const { themeSettings } = useThemeSettings(appBridge); - const templateThemeSettings = themeSettings?.[template] ?? {}; - const [pageTemplateSettings, setPageTemplateSettings] = useState>(null); - const [isLoading, setIsLoading] = useState(false); - const [customizedPageTemplateSettingsKeys, setCustomizedPageTemplateSettingsKeys] = useState([]); - const [mergedThemeAndPageTemplateSettings, setMergedThemeAndPageTemplateSettings] = - useState>>(null); - - useEffect(() => { - const updateBlockSettingsFromEvent = (event: EmitterEvents['AppBridge:PageTemplateSettingsUpdated']) => { - setPageTemplateSettings({ ...event.pageTemplateSettings } as TPageTemplateSettings); - }; - - const getInitialPageTemplateSettings = async () => { - setIsLoading(true); - let loadedSettings: Nullable = null; - - if (template === 'cover') { - loadedSettings = await appBridge.getCoverPageTemplateSettings(); - } else if (documentOrDocumentPageId === undefined) { - console.error(`Document ID is required for ${template} template settings`); - setMergedThemeAndPageTemplateSettings(null); - setIsLoading(false); - return; - } else if (template === 'documentPage') { - loadedSettings = - await appBridge.getDocumentPageTemplateSettings(documentOrDocumentPageId); - } else if (template === 'library') { - loadedSettings = - await appBridge.getLibraryPageTemplateSettings(documentOrDocumentPageId); - } - - setIsLoading(false); - setPageTemplateSettings(loadedSettings); - }; - - getInitialPageTemplateSettings(); - - window.emitter.on('AppBridge:PageTemplateSettingsUpdated', updateBlockSettingsFromEvent); - - return () => { - window.emitter.off('AppBridge:PageTemplateSettingsUpdated', updateBlockSettingsFromEvent); - }; - }, [appBridge, documentOrDocumentPageId, template]); - - useEffect(() => { - if (!templateThemeSettings || !pageTemplateSettings) { - return; - } - - const overrides = []; - const mergedSettings: Record & TPageTemplateSettings = { ...pageTemplateSettings }; - - for (const field of Object.keys(templateThemeSettings)) { - if ( - (pageTemplateSettings as Record)[field] !== null && - (pageTemplateSettings as Record)[field] !== undefined - ) { - overrides.push(field); - } else { - (mergedSettings as Record)[field] = templateThemeSettings[field]; - } - } - - setCustomizedPageTemplateSettingsKeys(overrides); - setMergedThemeAndPageTemplateSettings(mergedSettings); - }, [themeSettings, pageTemplateSettings]); - - const updatePageTemplateSettings = async (pageTemplateSettingsUpdate: Partial) => { - try { - if (template === 'cover') { - await appBridge.updateCoverPageTemplateSettings(pageTemplateSettingsUpdate); - } else if (documentOrDocumentPageId === undefined) { - console.error(`Document ID is required for ${template} template settings`); - setIsLoading(false); - return; - } else if (template === 'documentPage') { - await appBridge.updateDocumentPageTemplateSettings( - documentOrDocumentPageId, - pageTemplateSettingsUpdate, - ); - } else if (template === 'library') { - await appBridge.updateLibraryPageTemplateSettings(documentOrDocumentPageId, pageTemplateSettingsUpdate); - } - - window.emitter.emit('AppBridge:PageTemplateSettingsUpdated', { - pageTemplateSettings: { - ...pageTemplateSettings, - ...pageTemplateSettingsUpdate, - }, - }); - } catch (error) { - console.error(error); - } - }; - - return { - pageTemplateSettings: mergedThemeAndPageTemplateSettings, - templateThemeSettings, - customizedPageTemplateSettingsKeys, - updatePageTemplateSettings, - isLoading, - }; -}; diff --git a/packages/app-bridge/src/react/usePlatformAppBridge.spec.ts b/packages/app-bridge/src/react/usePlatformAppBridge.spec.ts deleted file mode 100644 index 31a75d250..000000000 --- a/packages/app-bridge/src/react/usePlatformAppBridge.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { usePlatformAppBridge } from './usePlatformAppBridge'; - -describe('usePlatformAppBridge', () => { - const TOKEN = 'AjY34F87Dsat^J'; - - window.location.search = `?token=${TOKEN}`; - vi.mock('../utilities/subscribe', () => ({ - subscribe: vi.fn().mockResolvedValue({ - statePort: { onmessage: vi.fn() }, - apiPort: { onmessage: vi.fn() }, - context: { parentId: 'parentId-test', connected: true }, - state: { settings: 'settings-test' }, - }), - })); - - vi.mock('../utilities/notify', () => ({ - notify: vi.fn(), - })); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should return undefined platformApp if not initiated', async () => { - const { result } = renderHook(() => usePlatformAppBridge()); - expect(result.current).toBeUndefined(); - }); - - it('should return platformApp after initiation and waiting', async () => { - const { result } = renderHook(() => usePlatformAppBridge()); - await waitFor(() => { - expect(result.current).toBeDefined(); - }); - }); -}); diff --git a/packages/app-bridge/src/react/usePlatformAppBridge.ts b/packages/app-bridge/src/react/usePlatformAppBridge.ts deleted file mode 100644 index db8e587fb..000000000 --- a/packages/app-bridge/src/react/usePlatformAppBridge.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { AppBridgePlatformApp } from '../AppBridgePlatformApp'; - -export const usePlatformAppBridge = () => { - const [platformAppBridge, setPlatformAppBridge] = useState(); - - useEffect(() => { - (async () => { - const appBridge = new AppBridgePlatformApp(); - appBridge.subscribe('Context.connected', () => { - setPlatformAppBridge(appBridge); - }); - appBridge.dispatch({ name: 'openConnection' }); - })(); - }, []); - return platformAppBridge; -}; diff --git a/packages/app-bridge/src/react/usePortalNavigation.spec.ts b/packages/app-bridge/src/react/usePortalNavigation.spec.ts deleted file mode 100644 index 97f526073..000000000 --- a/packages/app-bridge/src/react/usePortalNavigation.spec.ts +++ /dev/null @@ -1,314 +0,0 @@ -// @vitest-environment happy-dom - -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import { PortalNavigationTreeDummy, getAppBridgeThemeStub } from '../tests'; - -import { usePortalNavigation } from './usePortalNavigation'; - -const DEFAULT_NAVIGATION_TREE = PortalNavigationTreeDummy.default(); - -describe('usePortalNavigation', () => { - it('should return the preview and refetch when the cover page is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - const coverPageIndex = DEFAULT_NAVIGATION_TREE.findIndex((item) => item.type === 'cover-page'); - const updatedNavigationTree = [...DEFAULT_NAVIGATION_TREE]; - updatedNavigationTree.splice(coverPageIndex, 1); - - spy.mockResolvedValueOnce(updatedNavigationTree); - - // Trigger a "cover page deleted" event - window.emitter.emit('AppBridge:GuidelineCoverPage:Action', { action: 'delete' }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - expect(result.current.navigationItems).toEqual(updatedNavigationTree); - }); - }); - - it('should return the preview and refetch when a document group is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - const groupIndex = DEFAULT_NAVIGATION_TREE.findIndex((item) => item.id() === 200); - const updatedNavigationTree = [...DEFAULT_NAVIGATION_TREE]; - updatedNavigationTree.splice(groupIndex, 1); - - spy.mockResolvedValueOnce(updatedNavigationTree); - - // Trigger a "document group deleted" event - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: { id: 200 }, - action: 'delete', - }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - expect(result.current.navigationItems).toEqual(updatedNavigationTree); - }); - }); - - it('should return the previous tree and refetch when an incorrect document group id is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValue(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - // Trigger a "document group deleted" event - window.emitter.emit('AppBridge:GuidelineDocumentGroup:Action', { - documentGroup: { id: 2000 }, - action: 'delete', - }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - }); - }); - - it('should return the preview and refetch when a document at root level is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - const documentIndex = DEFAULT_NAVIGATION_TREE.findIndex((item) => item.id() === 101); - const updatedNavigationTree = [...DEFAULT_NAVIGATION_TREE]; - updatedNavigationTree.splice(documentIndex, 1); - - spy.mockResolvedValueOnce(updatedNavigationTree); - - // Trigger a "document deleted" event - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { id: 101 }, - action: 'delete', - }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - expect(result.current.navigationItems).toEqual(updatedNavigationTree); - }); - }); - - it('should return the same tree and refetch when a document within a group is deleted', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValue(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - // Trigger a "document deleted" event - window.emitter.emit('AppBridge:GuidelineDocument:Action', { - document: { id: 201, documentGroupId: 200 }, - action: 'delete', - }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - }); - - it('should fetch the navigation tree on mount', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - }); - - it('should refetch if targets in documents change', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValue(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - // Trigger a "document targets changed" event - window.emitter.emit('AppBridge:GuidelineDocumentTargets:Action', { - payload: { targets: [], documentIds: [] }, - action: 'update', - }); - - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(2); - expect(result.current.isLoading).toBe(false); - }); - }); - - it('should show document group in new position and refetch when a document group is moved', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - const NEW_POSITION = 1; - const groupIndex = DEFAULT_NAVIGATION_TREE.findIndex((item) => item.id() === 200); - const updatedNavigationTree = [...DEFAULT_NAVIGATION_TREE]; - const movedGroup = updatedNavigationTree.splice(groupIndex, 1); - updatedNavigationTree.splice(NEW_POSITION, 0, ...movedGroup); - - spy.mockResolvedValueOnce(updatedNavigationTree); - - // Trigger a "document group is moved" event - window.emitter.emit('AppBridge:GuidelineDocumentGroup:MoveEvent', { - action: 'movePreview', - position: NEW_POSITION, - documentGroup: { id: 200, sort: 3 }, - }); - - await waitFor(() => { - expect(result.current.navigationItems).toEqual(updatedNavigationTree); - }); - }); - - it('should show document in new position when a document is moved at root level', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - const NEW_POSITION = 2; - const documentIndex = DEFAULT_NAVIGATION_TREE.findIndex((item) => item.id() === 121); - const updatedNavigationTree = [...DEFAULT_NAVIGATION_TREE]; - const movedDocument = updatedNavigationTree.splice(documentIndex, 1); - updatedNavigationTree.splice(NEW_POSITION, 0, ...movedDocument); - - // Trigger a "document is moved" event - window.emitter.emit('AppBridge:GuidelineDocument:MoveEvent', { - action: 'movePreview', - position: NEW_POSITION, - document: { id: 121, sort: 5 }, - }); - - await waitFor(() => { - expect(result.current.navigationItems).toEqual(updatedNavigationTree); - }); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - }); - - it('should return the same tree when a document is moved within/in/out a group', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'api'); - - spy.mockResolvedValueOnce(DEFAULT_NAVIGATION_TREE); - - const { result } = renderHook(() => usePortalNavigation(appBridge)); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - const NEW_POSITION = 2; - - // Trigger a "document is moved" event - window.emitter.emit('AppBridge:GuidelineDocument:MoveEvent', { - action: 'movePreview', - position: NEW_POSITION, - document: { id: 201, sort: 2, documentGroupId: 200 }, - newGroupId: undefined, - }); - - await waitFor(() => { - expect(result.current.navigationItems).toEqual(DEFAULT_NAVIGATION_TREE); - }); - - expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith({ name: 'getPortalNavigation' }); - }); -}); diff --git a/packages/app-bridge/src/react/usePortalNavigation.ts b/packages/app-bridge/src/react/usePortalNavigation.ts deleted file mode 100644 index dc4140b55..000000000 --- a/packages/app-bridge/src/react/usePortalNavigation.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { produce } from 'immer'; -import debounce from 'lodash-es/debounce'; -import { useCallback, useEffect, useMemo, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type EmitterEvents } from '../types/Emitter'; -import { type PortalNavigationItem } from '../types/Guideline'; - -type Options = { - /** - * Whether it should fetch on mount. - */ - enabled?: boolean; -}; - -export const usePortalNavigation = (appBridge: AppBridgeTheme, options: Options = { enabled: true }) => { - const [navigationItems, setNavigationItems] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const refetch = useCallback(async () => { - setIsLoading(true); - setNavigationItems(await appBridge.api({ name: 'getPortalNavigation' })); - setIsLoading(false); - }, [appBridge]); - - const debouncedRefetch = useMemo( - () => - debounce(() => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refetch(); - }, 100), - [refetch], - ); - - useEffect(() => { - if (options.enabled) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refetch(); - } - }, [options.enabled, refetch]); - - useEffect(() => { - const handleCoverPageAction = (event: EmitterEvents['AppBridge:GuidelineCoverPage:Action']) => { - if (event.action === 'delete') { - setNavigationItems( - produce((draft) => { - return deleteItem(draft, 'cover-page', null); - }), - ); - } - - debouncedRefetch(); - }; - - const handleDocumentGroupAction = (event: EmitterEvents['AppBridge:GuidelineDocumentGroup:Action']) => { - if (event.action === 'delete') { - setNavigationItems( - produce((draft) => { - return deleteItem(draft, 'document-group', event.documentGroup.id); - }), - ); - } - - debouncedRefetch(); - }; - - const handleDocumentAction = (event: EmitterEvents['AppBridge:GuidelineDocument:Action']) => { - if (event.action === 'delete') { - setNavigationItems( - produce((draft) => { - if (!event.document.documentGroupId) { - return deleteItem(draft, 'document', event.document.id); - } - return draft; - }), - ); - } - - debouncedRefetch(); - }; - - const handleDocumentGroupMoveEventPreview = ( - event: EmitterEvents['AppBridge:GuidelineDocumentGroup:MoveEvent'], - ) => { - setNavigationItems( - produce((draft) => previewItemPosition(draft, event.documentGroup, event.position, ['document-group'])), - ); - }; - - const handleDocumentMoveEventPreview = (event: EmitterEvents['AppBridge:GuidelineDocument:MoveEvent']) => { - if (!event.document.documentGroupId && !event.newGroupId) { - setNavigationItems( - produce((draft) => - previewItemPosition(draft, event.document, event.position, [ - 'document', - 'document-library', - 'document-link', - ]), - ), - ); - } - }; - - window.emitter.on('AppBridge:GuidelineCoverPage:Action', handleCoverPageAction); - - window.emitter.on('AppBridge:GuidelineDocumentGroup:Action', handleDocumentGroupAction); - window.emitter.on('AppBridge:GuidelineDocumentGroup:MoveEvent', handleDocumentGroupMoveEventPreview); - - window.emitter.on('AppBridge:GuidelineDocument:Action', handleDocumentAction); - window.emitter.on('AppBridge:GuidelineDocument:MoveEvent', handleDocumentMoveEventPreview); - window.emitter.on('AppBridge:GuidelineDocumentTargets:Action', debouncedRefetch); - - return () => { - window.emitter.off('AppBridge:GuidelineCoverPage:Action', handleCoverPageAction); - - window.emitter.off('AppBridge:GuidelineDocumentGroup:Action', handleDocumentGroupAction); - window.emitter.off('AppBridge:GuidelineDocumentGroup:MoveEvent', handleDocumentGroupMoveEventPreview); - - window.emitter.off('AppBridge:GuidelineDocument:Action', handleDocumentAction); - window.emitter.off('AppBridge:GuidelineDocument:MoveEvent', handleDocumentMoveEventPreview); - window.emitter.off('AppBridge:GuidelineDocumentTargets:Action', debouncedRefetch); - }; - }, [navigationItems, debouncedRefetch]); - - return { navigationItems, refetch, isLoading }; -}; - -const deleteItem = (draft: PortalNavigationItem[], type: PortalNavigationItem['type'], itemId: Nullable) => { - const itemDraftIndex = draft.findIndex((item) => - !itemId ? item.type === type : item.type === type && item.id() === itemId, - ); - - if (itemDraftIndex !== -1) { - draft.splice(itemDraftIndex, 1); - } - - return draft; -}; - -const previewItemPosition = ( - draft: PortalNavigationItem[], - itemMoved: - | EmitterEvents['AppBridge:GuidelineDocument:MoveEvent']['document'] - | EmitterEvents['AppBridge:GuidelineDocumentGroup:MoveEvent']['documentGroup'], - newPosition: number, - allowedTypes: PortalNavigationItem['type'][], -) => { - const itemDraftIndex = draft.findIndex((item) => allowedTypes.includes(item.type) && item.id() === itemMoved.id); - - if (itemDraftIndex !== -1) { - const itemToMove = draft.splice(itemDraftIndex, 1); - draft.splice(newPosition, 0, ...itemToMove); - } - - return draft; -}; diff --git a/packages/app-bridge/src/react/useTemplateAssets.spec.ts b/packages/app-bridge/src/react/useTemplateAssets.spec.ts deleted file mode 100644 index 67e21c242..000000000 --- a/packages/app-bridge/src/react/useTemplateAssets.spec.ts +++ /dev/null @@ -1,445 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; -import { type SinonStub } from 'sinon'; -import { type Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { type ThemeTemplate } from '..'; -import { AssetDummy, getAppBridgeThemeStub } from '../tests'; - -import { useTemplateAssets } from './useTemplateAssets'; - -const DOCUMENT_ID = 16; -const DOCUMENT_PAGE_ID = 568; - -const THEME_ASSETS = { - key: [AssetDummy.with(3)], -}; - -const THEME_ASSETS_WITH_CUSTOM_KEY = { - customKey: [AssetDummy.with(2)], -}; - -const TEMPLATE_ASSETS = { - key: [AssetDummy.with(1)], -}; - -describe('useTemplateAssets hook', () => { - beforeEach(() => { - vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - cleanup(); - }); - - const loadUseTemplateAssets = async ( - template: ThemeTemplate, - existingTemplateAssets = [AssetDummy.with(1)], - existingThemeAssets = {}, - returnEmptyTemplateAssets = false, - ) => { - const asset = AssetDummy.with(1); - const appBridgeStub = getAppBridgeThemeStub({ - pageTemplateAssets: returnEmptyTemplateAssets ? {} : { key: existingTemplateAssets }, - themeAssets: existingThemeAssets, - }); - - const { result, rerender } = renderHook(() => - useTemplateAssets(appBridgeStub, template, DOCUMENT_ID, DOCUMENT_PAGE_ID), - ); - - await act(async () => { - rerender(); - }); - - return { result, appBridgeStub, asset }; - }; - - describe('documentPage template context', () => { - it('should delete an asset', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('documentPage', []); - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = appBridgeStub.deleteAssetIdsFromDocumentPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual(DOCUMENT_PAGE_ID); - expect(call.args[1]).toEqual('key'); - expect(call.lastArg).toEqual([1]); - expect(result.current.templateAssets).toStrictEqual({ key: [] }); - }); - }); - - it('should sort assets', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('documentPage', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - const deleteCall = appBridgeStub.deleteAssetIdsFromDocumentPageTemplateAssetKey.getCall(0); - const addCall = appBridgeStub.addAssetIdsToDocumentPageTemplateAssetKey.getCall(0); - - await waitFor(async () => { - expect(deleteCall.firstArg).toEqual(DOCUMENT_PAGE_ID); - expect(deleteCall.args[1]).toEqual('key'); - expect(deleteCall.lastArg).toEqual([1, 2]); - expect(addCall.firstArg).toEqual(DOCUMENT_PAGE_ID); - expect(addCall.args[1]).toEqual('key'); - expect(addCall.lastArg).toEqual([2, 1]); - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([2, 1]); - }); - }); - - it('should not sort assets if api call throws error', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('documentPage', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - (appBridgeStub.deleteAssetIdsFromDocumentPageTemplateAssetKey as unknown as Mock) = vi - .fn() - .mockRejectedValue('Unsuccessful API call'); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - await waitFor(async () => { - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([1, 2]); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('should notify about updated assets on delete', async () => { - const { result, asset } = await loadUseTemplateAssets('documentPage'); - - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('documentPage'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toStrictEqual({ key: [] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - - it('should add asset ids', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('documentPage'); - await act(async () => { - await result.current.addAssetIdsToKey('key', [2]); - }); - - const call = appBridgeStub.addAssetIdsToDocumentPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual(DOCUMENT_PAGE_ID); - expect(call.args[1]).toEqual('key'); - expect(call.lastArg).toEqual([2]); - expect(result.current.templateAssets.key.map(({ id }) => id)).toEqual([1, 2]); - }); - }); - - it('should notify about updated assets on add asset ids to key', async () => { - const { result, asset } = await loadUseTemplateAssets('documentPage'); - const assetToAdd = AssetDummy.with(2); - await act(async () => { - await result.current.addAssetIdsToKey('key', [assetToAdd.id]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('documentPage'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toMatchObject({ key: [asset, assetToAdd] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - }); - - describe('library template context', () => { - it('should delete an asset', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('library', []); - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = appBridgeStub.deleteAssetIdsFromLibraryPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual(DOCUMENT_ID); - expect(call.args[1]).toEqual('key'); - expect(call.lastArg).toEqual([1]); - expect(result.current.templateAssets).toStrictEqual({ key: [] }); - }); - }); - - it('should sort assets', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('library', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - const deleteCall = appBridgeStub.deleteAssetIdsFromLibraryPageTemplateAssetKey.getCall(0); - const addCall = appBridgeStub.addAssetIdsToLibraryPageTemplateAssetKey.getCall(0); - - await waitFor(async () => { - expect(deleteCall.firstArg).toEqual(DOCUMENT_ID); - expect(deleteCall.args[1]).toEqual('key'); - expect(deleteCall.lastArg).toEqual([1, 2]); - expect(addCall.firstArg).toEqual(DOCUMENT_ID); - expect(addCall.args[1]).toEqual('key'); - expect(addCall.lastArg).toEqual([2, 1]); - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([2, 1]); - }); - }); - - it('should not sort assets if api call throws error', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('library', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - (appBridgeStub.deleteAssetIdsFromLibraryPageTemplateAssetKey as unknown as Mock) = vi - .fn() - .mockRejectedValue('Unsuccessful API call'); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - await waitFor(async () => { - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([1, 2]); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('should notify about updated assets on delete', async () => { - const { result, asset } = await loadUseTemplateAssets('library'); - - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('library'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toStrictEqual({ key: [] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - - it('should add asset ids', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('library'); - await act(async () => { - await result.current.addAssetIdsToKey('key', [2]); - }); - - const call = appBridgeStub.addAssetIdsToLibraryPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual(DOCUMENT_ID); - expect(call.args[1]).toEqual('key'); - expect(call.lastArg).toEqual([2]); - expect(result.current.templateAssets.key.map(({ id }) => id)).toEqual([1, 2]); - }); - }); - - it('should notify about updated assets on add asset ids to key', async () => { - const { result, asset } = await loadUseTemplateAssets('library'); - const assetToAdd = AssetDummy.with(2); - await act(async () => { - await result.current.addAssetIdsToKey('key', [assetToAdd.id]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('library'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toMatchObject({ key: [asset, assetToAdd] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - }); - - describe('cover template context', () => { - it('should delete an asset', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('cover', []); - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = appBridgeStub.deleteAssetIdsFromCoverPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('key'); - expect(call.lastArg).toEqual([1]); - expect(result.current.templateAssets).toStrictEqual({ key: [] }); - }); - }); - - it('should sort assets', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('cover', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - const deleteCall = appBridgeStub.deleteAssetIdsFromCoverPageTemplateAssetKey.getCall(0); - const addCall = appBridgeStub.addAssetIdsToCoverPageTemplateAssetKey.getCall(0); - - await waitFor(async () => { - expect(deleteCall.firstArg).toEqual('key'); - expect(deleteCall.lastArg).toEqual([1, 2]); - expect(addCall.firstArg).toEqual('key'); - expect(addCall.lastArg).toEqual([2, 1]); - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([2, 1]); - }); - }); - - it('should not sort assets if api call throws error', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('cover', [ - AssetDummy.with(1), - AssetDummy.with(2), - ]); - (appBridgeStub.deleteAssetIdsFromCoverPageTemplateAssetKey as unknown as Mock) = vi - .fn() - .mockRejectedValue('Unsuccessful API call'); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - await waitFor(async () => { - expect(result.current.templateAssets.key.map((asset) => asset.id)).toEqual([1, 2]); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('should notify about updated assets on delete', async () => { - const { result, asset } = await loadUseTemplateAssets('cover'); - - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('cover'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toStrictEqual({ key: [] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - - it('should add asset ids', async () => { - const { result, appBridgeStub } = await loadUseTemplateAssets('cover'); - await act(async () => { - await result.current.addAssetIdsToKey('key', [2]); - }); - - const call = appBridgeStub.addAssetIdsToCoverPageTemplateAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('key'); - expect(call.lastArg).toEqual([2]); - expect(result.current.templateAssets.key.map(({ id }) => id)).toEqual([1, 2]); - }); - }); - - it('should notify about updated assets on add asset ids to key', async () => { - const { result, asset } = await loadUseTemplateAssets('cover'); - const assetToAdd = AssetDummy.with(2); - await act(async () => { - await result.current.addAssetIdsToKey('key', [assetToAdd.id]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:TemplateAssetsUpdated'); - expect(call.lastArg.template).toEqual('cover'); - expect(call.lastArg.documentId).toEqual(DOCUMENT_ID); - expect(call.lastArg.documentPageId).toEqual(DOCUMENT_PAGE_ID); - expect(call.lastArg.templateAssets).toMatchObject({ key: [asset, assetToAdd] }); - expect(call.lastArg.prevTemplateAssets).toMatchObject({ key: [asset] }); - }); - }); - }); - - describe('Theme and Template assets overrides', () => { - it('returns the template assets merged with theme assets and the list of custom overrides', async () => { - const { result } = await loadUseTemplateAssets('cover', [AssetDummy.with(1)], THEME_ASSETS); - await waitFor(() => { - expect(result.current.templateAssets).toEqual({ - ...TEMPLATE_ASSETS, - }); - expect(result.current.customizedTemplateAssetsKeys).toEqual(['key']); - }); - }); - - it('returns the template assets merged with theme assets and no overrides', async () => { - const { result } = await loadUseTemplateAssets('cover', [AssetDummy.with(1)], THEME_ASSETS_WITH_CUSTOM_KEY); - await waitFor(() => { - expect(result.current.templateAssets).toEqual({ - ...THEME_ASSETS_WITH_CUSTOM_KEY, - ...TEMPLATE_ASSETS, - }); - expect(result.current.customizedTemplateAssetsKeys).toEqual([]); - }); - }); - - it('returns an empty object if no theme assets and no template assets', async () => { - const { result } = await loadUseTemplateAssets('cover', [], {}, true); - await waitFor(() => { - expect(result.current.templateAssets).toEqual({}); - expect(result.current.customizedTemplateAssetsKeys).toEqual([]); - }); - }); - - it('returns only the theme assets if no template assets', async () => { - const { result } = await loadUseTemplateAssets('cover', [], THEME_ASSETS, true); - await waitFor(() => { - expect(result.current.templateAssets).toEqual({ - ...THEME_ASSETS, - }); - expect(result.current.customizedTemplateAssetsKeys).toEqual([]); - }); - }); - - it('returns only the template assets if no theme assets', async () => { - const { result } = await loadUseTemplateAssets('cover', [AssetDummy.with(1)], {}, false); - await waitFor(() => { - expect(result.current.templateAssets).toEqual({ - ...TEMPLATE_ASSETS, - }); - expect(result.current.customizedTemplateAssetsKeys).toEqual([]); - }); - }); - }); -}); diff --git a/packages/app-bridge/src/react/useTemplateAssets.ts b/packages/app-bridge/src/react/useTemplateAssets.ts deleted file mode 100644 index dcc8a4a43..000000000 --- a/packages/app-bridge/src/react/useTemplateAssets.ts +++ /dev/null @@ -1,208 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type Asset, type ThemeTemplate } from '../types'; -import { compareObjects } from '../utilities'; - -import { useThemeAssets } from './'; - -type HookArgs = { - appBridge: AppBridgeTheme; - template?: ThemeTemplate; - documentId?: number; - documentPageId?: number; -}; - -const getTemplateAssets = async ({ - appBridge, - template, - documentId, - documentPageId, -}: HookArgs): Promise> => { - if (template === 'cover') { - return await appBridge.getCoverPageTemplateAssets(); - } else if (template === 'documentPage' && documentPageId) { - return await appBridge.getDocumentPageTemplateAssets(documentPageId); - } else if (template === 'library' && documentId) { - return await appBridge.getLibraryPageTemplateAssets(documentId); - } - - return {}; -}; - -const addAssetIdsToTemplateAssetKey = async ( - key: string, - assetIds: number[], - { appBridge, template, documentId, documentPageId }: HookArgs, -): Promise => { - if (template === 'cover') { - await appBridge.addAssetIdsToCoverPageTemplateAssetKey(key, assetIds); - } else if (template === 'documentPage' && documentPageId) { - await appBridge.addAssetIdsToDocumentPageTemplateAssetKey(documentPageId, key, assetIds); - } else if (template === 'library' && documentId) { - await appBridge.addAssetIdsToLibraryPageTemplateAssetKey(documentId, key, assetIds); - } -}; - -const deleteAssetIdsFromTemplateAssetKey = async ( - key: string, - assetIds: number[], - { appBridge, template, documentId, documentPageId }: HookArgs, -): Promise => { - if (template === 'cover') { - await appBridge.deleteAssetIdsFromCoverPageTemplateAssetKey(key, assetIds); - } else if (template === 'documentPage' && documentPageId) { - await appBridge.deleteAssetIdsFromDocumentPageTemplateAssetKey(documentPageId, key, assetIds); - } else if (template === 'library' && documentId) { - await appBridge.deleteAssetIdsFromLibraryPageTemplateAssetKey(documentId, key, assetIds); - } -}; - -export const useTemplateAssets = ( - appBridge: AppBridgeTheme, - template: ThemeTemplate, - documentId?: number, - documentPageId?: number, -) => { - const [templateAssets, setTemplateAssets] = useState>({}); - const { themeAssets } = useThemeAssets(appBridge, template); - const [customizedTemplateAssetsKeys, setCustomizedTemplateAssetsKeys] = useState([]); - const [mergedThemeAndTemplateAssets, setMergedThemeAndTemplateAssets] = useState>({}); - - // Fetch the template assets on mount. - // And add listener for template assets updates. - useEffect(() => { - let componentMounted = true; - - const updateTemplateAssetsFromEvent = (event: { - template: ThemeTemplate; - documentId?: number; - documentPageId?: number; - templateAssets: Record; - prevTemplateAssets: Record; - }) => { - if ( - event.template === template && - event.documentId === documentId && - event.documentPageId === documentPageId && - !compareObjects(event.templateAssets, event.prevTemplateAssets) - ) { - setTemplateAssets(event.templateAssets); - } - }; - - if (template) { - const mountingFetch = async () => { - const allTemplateAssets = await getTemplateAssets({ - appBridge, - template, - documentId, - documentPageId, - }); - - if (componentMounted) { - setTemplateAssets(allTemplateAssets); - } - }; - mountingFetch(); - - window.emitter.on('AppBridge:TemplateAssetsUpdated', updateTemplateAssetsFromEvent); - } - - return () => { - componentMounted = false; - window.emitter.off('AppBridge:TemplateAssetsUpdated', updateTemplateAssetsFromEvent); - }; - }, [appBridge, documentId, documentPageId, template]); - - useEffect(() => { - const overrides = []; - const mergedAssets = { ...templateAssets }; - - for (const field in themeAssets) { - if (templateAssets[field] !== null && templateAssets[field] !== undefined) { - overrides.push(field); - } else if (themeAssets[field].length > 0) { - mergedAssets[field] = themeAssets[field]; - } - } - - setCustomizedTemplateAssetsKeys(overrides); - setMergedThemeAndTemplateAssets(mergedAssets); - }, [themeAssets, templateAssets]); - - const emitUpdatedTemplateAssets = async () => { - window.emitter.emit('AppBridge:TemplateAssetsUpdated', { - template, - documentId, - documentPageId, - templateAssets: await getTemplateAssets({ - appBridge, - template, - documentId, - documentPageId, - }), - prevTemplateAssets: { ...templateAssets }, - }); - }; - - const updateAssetIdsFromKey = async (key: string, newAssetIds: number[]) => { - const currentTemplateAssets = await getTemplateAssets({ - appBridge, - template, - documentId, - documentPageId, - }); - const oldAssetIds = currentTemplateAssets[key]?.map((asset) => asset.id) ?? []; - - try { - await deleteAssetIdsFromTemplateAssetKey(key, oldAssetIds, { - appBridge, - template, - documentId, - documentPageId, - }); - await addAssetIdsToTemplateAssetKey(key, newAssetIds, { - appBridge, - template, - documentId, - documentPageId, - }); - } catch (error) { - console.error(error); - } - - emitUpdatedTemplateAssets(); - }; - - const deleteAssetIdsFromKey = async (key: string, assetIds: number[]) => { - await deleteAssetIdsFromTemplateAssetKey(key, assetIds, { - appBridge, - template, - documentId, - documentPageId, - }); - emitUpdatedTemplateAssets(); - }; - - const addAssetIdsToKey = async (key: string, assetIds: number[]) => { - await addAssetIdsToTemplateAssetKey(key, assetIds, { - appBridge, - template, - documentId, - documentPageId, - }); - emitUpdatedTemplateAssets(); - }; - - return { - templateAssets: mergedThemeAndTemplateAssets, - themeAssets, - customizedTemplateAssetsKeys, - addAssetIdsToKey, - deleteAssetIdsFromKey, - updateAssetIdsFromKey, - }; -}; diff --git a/packages/app-bridge/src/react/useThemeAssets.spec.ts b/packages/app-bridge/src/react/useThemeAssets.spec.ts deleted file mode 100644 index 76d268249..000000000 --- a/packages/app-bridge/src/react/useThemeAssets.spec.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; -import { type SinonStub } from 'sinon'; -import { type Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { type Asset, type ThemeTemplate } from '..'; -import { AssetDummy, getAppBridgeThemeStub } from '../tests'; - -import { useThemeAssets } from './useThemeAssets'; - -const DOCUMENT_PAGE_TEMPLATE = 'documentPage'; - -describe('useThemeAssets hook', () => { - beforeEach(() => { - vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - cleanup(); - }); - - const loadUseThemeAssets = async (existingAssets: Asset[], template: ThemeTemplate, keyName: string) => { - const asset = AssetDummy.with(1); - const appBridgeStub = getAppBridgeThemeStub({ - themeAssets: { [keyName]: existingAssets }, - }); - - const { result, rerender } = renderHook(() => useThemeAssets(appBridgeStub, template)); - - // eslint-disable-next-line @typescript-eslint/require-await - await act(async () => { - rerender(); - }); - - return { result, appBridgeStub, asset }; - }; - - it('should delete an asset', async () => { - const { result, appBridgeStub } = await loadUseThemeAssets([], DOCUMENT_PAGE_TEMPLATE, 'key'); - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = appBridgeStub.deleteAssetIdsFromThemeAssetKey.getCall(0); - - await waitFor(() => { - expect(call.firstArg).toEqual('key'); - expect(call.args[1]).toEqual([1]); - expect(call.lastArg).toEqual(DOCUMENT_PAGE_TEMPLATE); - expect(result.current.themeAssets).toStrictEqual({ key: [] }); - }); - }); - - it('should sort assets', async () => { - const { result, appBridgeStub } = await loadUseThemeAssets( - [AssetDummy.with(1), AssetDummy.with(2)], - DOCUMENT_PAGE_TEMPLATE, - 'key', - ); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - const deleteCall = appBridgeStub.deleteAssetIdsFromThemeAssetKey.getCall(0); - const addCall = appBridgeStub.addAssetIdsToThemeAssetKey.getCall(0); - - await waitFor(async () => { - expect(deleteCall.firstArg).toEqual('key'); - expect(deleteCall.args[1]).toEqual([1, 2]); - expect(addCall.firstArg).toEqual('key'); - expect(addCall.args[1]).toEqual([2, 1]); - expect(result.current.themeAssets.key.map((asset) => asset.id)).toEqual([2, 1]); - }); - }); - - it('should delete nothing if key does not exist', async () => { - const { result, appBridgeStub } = await loadUseThemeAssets( - [AssetDummy.with(1), AssetDummy.with(2)], - DOCUMENT_PAGE_TEMPLATE, - 'non-existing-key', - ); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - const deleteCall = appBridgeStub.deleteAssetIdsFromThemeAssetKey.getCall(0); - - await waitFor(async () => { - expect(deleteCall.firstArg).toEqual('key'); - expect(deleteCall.args[1]).toEqual([]); - }); - }); - - it('should not sort assets if api call throws error', async () => { - const { result, appBridgeStub } = await loadUseThemeAssets( - [AssetDummy.with(1), AssetDummy.with(2)], - DOCUMENT_PAGE_TEMPLATE, - 'key', - ); - (appBridgeStub.deleteAssetIdsFromThemeAssetKey as unknown as Mock) = vi - .fn() - .mockRejectedValue('Unsuccessful API call'); - - await act(async () => { - await result.current.updateAssetIdsFromKey('key', [2, 1]); - }); - - await waitFor(async () => { - expect(result.current.themeAssets.key.map((asset) => asset.id)).toEqual([1, 2]); - }); - - expect(console.error).toHaveBeenCalledOnce(); - }); - - it('should notify about updated assets on delete', async () => { - const { result, asset } = await loadUseThemeAssets([AssetDummy.with(1)], DOCUMENT_PAGE_TEMPLATE, 'key'); - - await act(async () => { - await result.current.deleteAssetIdsFromKey('key', [1]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:ThemeAssetsUpdated'); - expect(call.lastArg.themeAssets).toStrictEqual({ key: [] }); - expect(call.lastArg.prevThemeAssets).toMatchObject({ key: [asset] }); - }); - }); - - it('should add asset ids', async () => { - const { result, appBridgeStub } = await loadUseThemeAssets([AssetDummy.with(1)], DOCUMENT_PAGE_TEMPLATE, 'key'); - await act(async () => { - await result.current.addAssetIdsToKey('key', [2]); - }); - - const call = appBridgeStub.addAssetIdsToThemeAssetKey.getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('key'); - expect(call.args[1]).toEqual([2]); - expect(result.current.themeAssets.key.map(({ id }) => id)).toEqual([1, 2]); - }); - }); - - it('should notify about updated assets on add asset ids to key', async () => { - const { result, asset } = await loadUseThemeAssets([AssetDummy.with(1)], DOCUMENT_PAGE_TEMPLATE, 'key'); - const assetToAdd = AssetDummy.with(2); - await act(async () => { - await result.current.addAssetIdsToKey('key', [assetToAdd.id]); - }); - - const call = (window.emitter.emit as SinonStub).getCall(0); - await waitFor(() => { - expect(call.firstArg).toEqual('AppBridge:ThemeAssetsUpdated'); - expect(call.lastArg.themeAssets).toMatchObject({ key: [asset, assetToAdd] }); - expect(call.lastArg.prevThemeAssets).toMatchObject({ key: [asset] }); - }); - }); -}); diff --git a/packages/app-bridge/src/react/useThemeAssets.ts b/packages/app-bridge/src/react/useThemeAssets.ts deleted file mode 100644 index 051ad1478..000000000 --- a/packages/app-bridge/src/react/useThemeAssets.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type Asset, type ThemeTemplate } from '../types'; -import { compareObjects } from '../utilities'; - -export const useThemeAssets = (appBridge: AppBridgeTheme, template?: ThemeTemplate) => { - const portalId = appBridge.getPortalId(); - - const [themeAssets, setThemeAssets] = useState>({}); - - const updateThemeAssetsFromEvent = (event: { - template?: ThemeTemplate; - portalId: number; - themeAssets: Record; - prevThemeAssets: Record; - }) => { - if ( - template && - event.template === template && - event.portalId === portalId && - !compareObjects(event.themeAssets, event.prevThemeAssets) - ) { - setThemeAssets(event.themeAssets); - } - }; - - const getThemeAssets = async (): Promise> => { - return template ? await appBridge.getThemeAssets(template) : Promise.resolve({}); - }; - - const addAssetIdsToThemeAssetKey = async (key: string, assetIds: number[]): Promise => { - if (template) { - await appBridge.addAssetIdsToThemeAssetKey(key, assetIds, template); - } - }; - - const deleteAssetIdsFromThemeAssetKey = async (key: string, assetIds: number[]): Promise => { - if (template) { - await appBridge.deleteAssetIdsFromThemeAssetKey(key, assetIds, template); - } - }; - - // Fetch the theme assets on mount. - // And add listener for theme assets updates. - useEffect(() => { - let componentMounted = true; - - if (portalId) { - const mountingFetch = async () => { - const allThemeAssets = template ? await getThemeAssets() : {}; - - if (componentMounted && template) { - setThemeAssets(allThemeAssets); - } - }; - mountingFetch(); - - window.emitter.on('AppBridge:ThemeAssetsUpdated', updateThemeAssetsFromEvent); - } - - return () => { - componentMounted = false; - window.emitter.off('AppBridge:ThemeAssetsUpdated', updateThemeAssetsFromEvent); - }; - }, [appBridge]); - - const emitUpdatedThemeAssets = async () => { - if (template) { - window.emitter.emit('AppBridge:ThemeAssetsUpdated', { - portalId, - themeAssets: await getThemeAssets(), - prevThemeAssets: { ...themeAssets }, - template, - }); - } - }; - - const updateAssetIdsFromKey = async (key: string, newAssetIds: number[]) => { - const currentThemeAssets = await getThemeAssets(); - const oldAssetIds = currentThemeAssets[key]?.map((asset) => asset.id) ?? []; - - try { - await deleteAssetIdsFromThemeAssetKey(key, oldAssetIds); - await addAssetIdsToThemeAssetKey(key, newAssetIds); - } catch (error) { - console.error(error); - } - - emitUpdatedThemeAssets(); - }; - - const deleteAssetIdsFromKey = async (key: string, assetIds: number[]) => { - await deleteAssetIdsFromThemeAssetKey(key, assetIds); - emitUpdatedThemeAssets(); - }; - - const addAssetIdsToKey = async (key: string, assetIds: number[]) => { - await addAssetIdsToThemeAssetKey(key, assetIds); - emitUpdatedThemeAssets(); - }; - - return { - themeAssets, - addAssetIdsToKey, - deleteAssetIdsFromKey, - updateAssetIdsFromKey, - }; -}; diff --git a/packages/app-bridge/src/react/useThemeSettings.spec.ts b/packages/app-bridge/src/react/useThemeSettings.spec.ts deleted file mode 100644 index c98c2e130..000000000 --- a/packages/app-bridge/src/react/useThemeSettings.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { cleanup, renderHook, waitFor } from '@testing-library/react'; -import { type SinonStub } from 'sinon'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { getAppBridgeThemeStub } from '../tests'; -import { type ThemeTemplate } from '../types'; - -import { useThemeSettings } from './useThemeSettings'; - -const THEME_SETTINGS = { - cover: { - coverSettingOne: 'value one', - coverSettingTwo: 'value two', - }, - documentPage: {}, - library: {}, -}; - -describe('useThemeSettings', () => { - beforeEach(() => { - vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - cleanup(); - }); - - const loadUseThemeSettings = async (themeSettings: Record>) => { - const appBridgeStub = getAppBridgeThemeStub({ - themeSettings, - }); - - const { result } = renderHook(() => useThemeSettings(appBridgeStub)); - return { result, appBridgeStub }; - }; - - it('returns the theme settings', async () => { - const { result } = await loadUseThemeSettings(THEME_SETTINGS); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.themeSettings).toEqual(THEME_SETTINGS); - }); - }); - - it('updates the theme settings', async () => { - const { result } = await loadUseThemeSettings(THEME_SETTINGS); - - expect(result.current.isLoading).toEqual(true); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.themeSettings).toEqual(THEME_SETTINGS); - }); - - await result.current.updateThemeSettings({ - cover: { coverSettingTwo: 'value two - updated' }, - }); - expect(result.current.isLoading).toEqual(false); - - const emitCall = (window.emitter.emit as SinonStub).getCall(0); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - expect(result.current.themeSettings).toEqual({ - cover: { coverSettingOne: 'value one', coverSettingTwo: 'value two - updated' }, - documentPage: {}, - library: {}, - }); - expect(emitCall.firstArg).toEqual('AppBridge:ThemeSettingsUpdated'); - expect(emitCall.lastArg.themeSettings).toStrictEqual({ - cover: { coverSettingOne: 'value one', coverSettingTwo: 'value two - updated' }, - documentPage: {}, - library: {}, - }); - }); - }); -}); diff --git a/packages/app-bridge/src/react/useThemeSettings.ts b/packages/app-bridge/src/react/useThemeSettings.ts deleted file mode 100644 index 3aa6b76c4..000000000 --- a/packages/app-bridge/src/react/useThemeSettings.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { useEffect, useState } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type EmitterEvents, type ThemeTemplate } from '../types'; - -export const useThemeSettings = >>( - appBridge: AppBridgeTheme, -) => { - const [themeSettings, setThemeSettings] = useState>(null); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const updateThemeSettingsFromEvent = (event: EmitterEvents['AppBridge:ThemeSettingsUpdated']) => { - setThemeSettings({ ...event.themeSettings } as T); - }; - - const getInitialThemeSettings = async () => { - setIsLoading(true); - - const themeSettings = await appBridge.getThemeSettings(); - setThemeSettings(themeSettings); - - setIsLoading(false); - }; - - getInitialThemeSettings(); - - window.emitter.on('AppBridge:ThemeSettingsUpdated', updateThemeSettingsFromEvent); - - return () => { - window.emitter.off('AppBridge:ThemeSettingsUpdated', updateThemeSettingsFromEvent); - }; - }, [appBridge]); - - const updateThemeSettings = async (themeSettingsUpdate: Partial) => { - try { - await appBridge.updateThemeSettings(themeSettingsUpdate); - - window.emitter.emit('AppBridge:ThemeSettingsUpdated', { - themeSettings: { - cover: { ...themeSettings?.cover, ...themeSettingsUpdate.cover }, - library: { ...themeSettings?.library, ...themeSettingsUpdate.library }, - documentPage: { ...themeSettings?.documentPage, ...themeSettingsUpdate.documentPage }, - }, - }); - } catch (error) { - console.error(error); - } - }; - - return { themeSettings, updateThemeSettings, isLoading }; -}; diff --git a/packages/app-bridge/src/react/useUncategorizedDocumentPages.spec.ts b/packages/app-bridge/src/react/useUncategorizedDocumentPages.spec.ts deleted file mode 100644 index 14b50d140..000000000 --- a/packages/app-bridge/src/react/useUncategorizedDocumentPages.spec.ts +++ /dev/null @@ -1,589 +0,0 @@ -// @vitest-environment happy-dom - -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { renderHook, waitFor } from '@testing-library/react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { DocumentPageDummy, getAppBridgeThemeStub } from '../tests'; -import { type DocumentPage } from '../types'; - -import { useUncategorizedDocumentPages } from './useUncategorizedDocumentPages'; - -const DOCUMENT_ID = 345346; -const ANOTHER_DOCUMENT_ID = 49385; -const DOCUMENT_PAGE_ID_1 = 24324; -const DOCUMENT_PAGE_ID_2 = 3532; -const DOCUMENT_PAGE_ID_3 = 98954; -const DOCUMENT_PAGE_ID_4 = 68445; -const DOCUMENT_CATEGORY_ID = 34656; - -describe('useUncategorizedDocumentPages', () => { - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should fetch document pages on mount', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(DOCUMENT_ID); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]); - }); - }); - - it('should not fetch document pages on mount if not enabled', () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID, { enabled: false })); - - expect(result.current.isLoading).toBe(true); - expect(spy).not.toHaveBeenCalled(); - expect(result.current.documentPages).toEqual([]); - }); - - it('should fetch document pages if it gets enabled', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - let enabled = false; - - const { result, rerender } = renderHook(() => - useUncategorizedDocumentPages(appBridge, DOCUMENT_ID, { enabled }), - ); - - expect(result.current.isLoading).toBe(true); - expect(spy).not.toHaveBeenCalled(); - expect(result.current.documentPages).toEqual([]); - - enabled = true; - - rerender(); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalled(); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]); - }); - }); - - it('should update document pages if a page is added in the current document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID, - categoryId: null, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Mock the response of the second call - spy.mockImplementationOnce(() => - Promise.resolve([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_4, documentId: DOCUMENT_ID, categoryId: null }), - ]), - ); - - // Trigger a "document page added" event in the current document - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'add', - documentPage: DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledTimes(2); - }); - - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_4, documentId: DOCUMENT_ID, categoryId: null }), - ]); - }); - - it('should not update document pages if a page is added in another document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: ANOTHER_DOCUMENT_ID, - categoryId: null, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page added" event from another document - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'add', - documentPage: DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should not update the document pages if a page is added in a category', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: DOCUMENT_CATEGORY_ID, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page added" event from a category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'add', - documentPage: DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should update document pages if a page is removed from the document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page deleted" event in the current document - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'delete', - documentPage: { - id: DOCUMENT_PAGE.id, - documentId: DOCUMENT_PAGE.documentId, - categoryId: DOCUMENT_PAGE.categoryId, - }, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]); - }); - - it('should not update document pages if a page is removed from another document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: ANOTHER_DOCUMENT_ID, - categoryId: null, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page deleted" event in the current document - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'delete', - documentPage: { - id: DOCUMENT_PAGE.id, - documentId: DOCUMENT_PAGE.documentId, - categoryId: DOCUMENT_PAGE.categoryId, - }, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should not update the document pages if a page is removed from a category', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID, - categoryId: DOCUMENT_CATEGORY_ID, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page deleted" event from another category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'delete', - documentPage: { - id: DOCUMENT_PAGE.id, - documentId: DOCUMENT_PAGE.documentId, - categoryId: DOCUMENT_PAGE.categoryId, - }, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should update document pages if a page is updated in the document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const UPDATED_DOCUMENT_PAGE: DocumentPage = { - ...DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 2, - }), - title: 'Updated title', - }; - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Mock the response of the second call - spy.mockImplementationOnce(() => - Promise.resolve([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - UPDATED_DOCUMENT_PAGE, - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]), - ); - - // Trigger a "document page updated" event in the specified category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'update', - documentPage: UPDATED_DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledTimes(2); - }); - - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - UPDATED_DOCUMENT_PAGE, - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]); - }); - - it('should not update the document pages if a page is updated from another document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const UPDATED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: ANOTHER_DOCUMENT_ID, - categoryId: DOCUMENT_CATEGORY_ID, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page updated" event from a category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'update', - documentPage: UPDATED_DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should not update the document pages if a page is updated in a category', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const UPDATED_DOCUMENT_PAGE = DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_4, - documentId: DOCUMENT_ID, - categoryId: DOCUMENT_CATEGORY_ID, - }); - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Trigger a "document page updated" event from a category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'update', - documentPage: UPDATED_DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages.map((documentPage) => documentPage.id)).toEqual([ - DOCUMENT_PAGE_ID_1, - DOCUMENT_PAGE_ID_2, - DOCUMENT_PAGE_ID_3, - ]); - }); - - it('should update document pages if a page is moved in the document', async () => { - const appBridge = getAppBridgeThemeStub(); - const spy = vi.spyOn(appBridge, 'getUncategorizedDocumentPagesByDocumentId'); - - const UPDATED_DOCUMENT_PAGE: DocumentPage = { - ...DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_2, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 2, - }), - sort: 3, - }; - - const { result } = renderHook(() => useUncategorizedDocumentPages(appBridge, DOCUMENT_ID)); - - expect(result.current.isLoading).toBe(true); - expect(spy).toHaveBeenCalledOnce(); - - await waitFor(() => expect(result.current.isLoading).toBe(false)); - - // Mock the response of the second call - spy.mockImplementationOnce(() => - Promise.resolve([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - UPDATED_DOCUMENT_PAGE, - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - ]), - ); - - // Trigger a "document page updated" event in the specified category - window.emitter.emit('AppBridge:GuidelineDocumentPage:Action', { - action: 'move', - documentPage: UPDATED_DOCUMENT_PAGE, - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - expect(spy).toHaveBeenCalledOnce(); - }); - - expect(result.current.documentPages).toEqual([ - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_1, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: DOCUMENT_PAGE_ID_3, - documentId: DOCUMENT_ID, - categoryId: null, - sort: 3, - }), - UPDATED_DOCUMENT_PAGE, - ]); - }); -}); diff --git a/packages/app-bridge/src/react/useUncategorizedDocumentPages.ts b/packages/app-bridge/src/react/useUncategorizedDocumentPages.ts index 4d08b485f..c18587dd3 100644 --- a/packages/app-bridge/src/react/useUncategorizedDocumentPages.ts +++ b/packages/app-bridge/src/react/useUncategorizedDocumentPages.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type DocumentPage, type EmitterEvents } from '../types'; import { type DocumentPageTargetEvent } from './useDocumentPageTargets'; @@ -21,7 +20,7 @@ type Options = { const sortDocumentPages = (a: DocumentPage, b: DocumentPage) => (a.sort && b.sort ? a.sort - b.sort : 0); export const useUncategorizedDocumentPages = ( - appBridge: AppBridgeBlock | AppBridgeTheme, + appBridge: AppBridgeBlock, documentId: number, options: Options = { enabled: true }, ) => { @@ -162,7 +161,7 @@ const moveDocumentPage = (draft: Map, documentPage: Docume return draft; }; -const fetchDocumentPagesByDocumentId = async (appBridge: AppBridgeBlock | AppBridgeTheme, documentId: number) => { +const fetchDocumentPagesByDocumentId = async (appBridge: AppBridgeBlock, documentId: number) => { const pages = await appBridge.getUncategorizedDocumentPagesByDocumentId(documentId); return new Map([...pages].sort(sortDocumentPages).map((page) => [page.id, page])); }; diff --git a/packages/app-bridge/src/react/useUngroupedDocuments.ts b/packages/app-bridge/src/react/useUngroupedDocuments.ts index 7607e3683..305c2de2d 100644 --- a/packages/app-bridge/src/react/useUngroupedDocuments.ts +++ b/packages/app-bridge/src/react/useUngroupedDocuments.ts @@ -4,7 +4,6 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { type AppBridgeBlock } from '../AppBridgeBlock'; -import { type AppBridgeTheme } from '../AppBridgeTheme'; import { type Document, type EmitterEvents } from '../types'; type DocumentPageEvent = EmitterEvents['AppBridge:GuidelineDocument:DocumentPageAction']; @@ -22,10 +21,7 @@ type Options = { const sortDocuments = (a: Document, b: Document) => (a.sort && b.sort ? a.sort - b.sort : 0); -export const useUngroupedDocuments = ( - appBridge: AppBridgeBlock | AppBridgeTheme, - options: Options = { enabled: true }, -) => { +export const useUngroupedDocuments = (appBridge: AppBridgeBlock, options: Options = { enabled: true }) => { const [documents, setDocuments] = useState>(new Map([])); const [isLoading, setIsLoading] = useState(true); @@ -272,7 +268,7 @@ const actionHandlers = { default: (documents: Map) => documents, }; -const fetchUngroupedDocuments = async (appBridge: AppBridgeBlock | AppBridgeTheme) => { +const fetchUngroupedDocuments = async (appBridge: AppBridgeBlock) => { const documents = await appBridge.getUngroupedDocuments(); return new Map([...documents].sort(sortDocuments).map((document) => [document.id, document])); }; diff --git a/packages/app-bridge/src/registries/api/ApiMethodRegistry.ts b/packages/app-bridge/src/registries/api/ApiMethodRegistry.ts index 8cab26265..acc95fb7d 100644 --- a/packages/app-bridge/src/registries/api/ApiMethodRegistry.ts +++ b/packages/app-bridge/src/registries/api/ApiMethodRegistry.ts @@ -2,40 +2,24 @@ import { type ApiMethodNameValidator } from '../../AppBridge'; -import { type CreateAssetPayload, type CreateAssetResponse } from './CreateAsset'; import { type GetAssetBulkDownloadTokenPayload, type GetAssetBulkDownloadTokenResponse, } from './GetAssetBulkDownloadToken'; -import { - type GetAssetResourceInformationPayload, - type GetAssetResourceInformationResponse, -} from './GetAssetResourceInformation'; -import { type GetCurrentUserPayload, type GetCurrentUserResponse } from './GetCurrentUser'; -import { type GetDocumentNavigationPayload, type GetDocumentNavigationResponse } from './GetDocumentNavigation'; -import { type GetPortalNavigationResponse } from './GetPortalNavigation'; -import { type GetProxyNetworkCallPayload, type GetProxyNetworkCallResponse } from './GetProxyNetworkCall'; +import { type GetCurrentUserPayload, type GetCurrentUserResponse } from './GetCurrentUser.ts'; import { type SetAssetIdsByBlockAssetKeyPayload, type SetAssetIdsByBlockAssetKeyResponse, } from './SetAssetIdsByBlockAssetKey'; export type ApiMethodRegistry = ApiMethodNameValidator<{ + getCurrentUser: { payload: GetCurrentUserPayload; response: GetCurrentUserResponse }; getAssetBulkDownloadToken: { payload: GetAssetBulkDownloadTokenPayload; response: GetAssetBulkDownloadTokenResponse; }; - getCurrentUser: { payload: GetCurrentUserPayload; response: GetCurrentUserResponse }; - createAsset: { payload: CreateAssetPayload; response: CreateAssetResponse }; - getAssetResourceInformation: { - payload: GetAssetResourceInformationPayload; - response: GetAssetResourceInformationResponse; - }; setAssetIdsByBlockAssetKey: { payload: SetAssetIdsByBlockAssetKeyPayload; response: SetAssetIdsByBlockAssetKeyResponse; }; - getProxyNetworkCall: { payload: GetProxyNetworkCallPayload; response: GetProxyNetworkCallResponse }; - getDocumentNavigation: { payload: GetDocumentNavigationPayload; response: GetDocumentNavigationResponse }; - getPortalNavigation: { payload: void; response: GetPortalNavigationResponse }; }>; diff --git a/packages/app-bridge/src/registries/api/CreateAsset.spec.ts b/packages/app-bridge/src/registries/api/CreateAsset.spec.ts deleted file mode 100644 index c3c26abe6..000000000 --- a/packages/app-bridge/src/registries/api/CreateAsset.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { createAsset } from './CreateAsset'; - -describe('CreateAsset', () => { - it('should return correct method name and payload', () => { - const TEST_DATA = 'test'; - const payload = { - data: TEST_DATA, - filename: TEST_DATA, - parentId: TEST_DATA, - description: TEST_DATA, - externalId: TEST_DATA, - tags: [{ value: TEST_DATA }], - }; - const currentUser = createAsset(payload); - expect(currentUser.name).toBe('createAsset'); - expect(currentUser.payload).toBe(payload); - }); -}); diff --git a/packages/app-bridge/src/registries/api/CreateAsset.ts b/packages/app-bridge/src/registries/api/CreateAsset.ts deleted file mode 100644 index 9bfb8a445..000000000 --- a/packages/app-bridge/src/registries/api/CreateAsset.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type CreateAssetPayload = { - data: File | Blob | string; - filename: string; - parentId?: string; - description?: string; - externalId?: string; - tags?: { value: string }[]; -}; - -export type CreateAssetResponse = { - assetId: string; -}; - -export const createAsset = (payload: CreateAssetPayload): { name: 'createAsset'; payload: CreateAssetPayload } => ({ - name: 'createAsset', - payload, -}); diff --git a/packages/app-bridge/src/registries/api/GetAssetResourceInformation.spec.ts b/packages/app-bridge/src/registries/api/GetAssetResourceInformation.spec.ts deleted file mode 100644 index 3e2788fae..000000000 --- a/packages/app-bridge/src/registries/api/GetAssetResourceInformation.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { getAssetResourceInformation } from './GetAssetResourceInformation'; - -describe('GetAssetResourceInformation', () => { - it('should return correct method name', () => { - const TEST_ID = 'test-123'; - const assetResourceInformation = getAssetResourceInformation({ - assetId: TEST_ID, - }); - expect(assetResourceInformation.name).toBe('getAssetResourceInformation'); - expect(assetResourceInformation.payload).toStrictEqual({ - assetId: TEST_ID, - }); - }); -}); diff --git a/packages/app-bridge/src/registries/api/GetAssetResourceInformation.ts b/packages/app-bridge/src/registries/api/GetAssetResourceInformation.ts deleted file mode 100644 index ecfa5e9bb..000000000 --- a/packages/app-bridge/src/registries/api/GetAssetResourceInformation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type GetAssetResourceInformationPayload = { - assetId: string; -}; - -export type GetAssetResourceInformationResponse = { - type: string; - id: string; - title?: string; - previewUrl?: string; - downloadUrl?: string; - filename?: string | null; - sourceUrl?: string; - html?: string; -}; - -export const getAssetResourceInformation = ( - payload: GetAssetResourceInformationPayload, -): { name: 'getAssetResourceInformation'; payload: GetAssetResourceInformationPayload } => ({ - name: 'getAssetResourceInformation', - payload, -}); diff --git a/packages/app-bridge/src/registries/api/GetDocumentNavigation.spec.ts b/packages/app-bridge/src/registries/api/GetDocumentNavigation.spec.ts deleted file mode 100644 index be862a687..000000000 --- a/packages/app-bridge/src/registries/api/GetDocumentNavigation.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { GuidelineDocumentClass, GuidelineDocumentClassDummy } from '../../tests'; - -import { getDocumentNavigation } from './GetDocumentNavigation'; - -describe('GetDocumentNavigation', () => { - it('should return correct method name', () => { - const document = GuidelineDocumentClassDummy.with(101, 'document-101'); - - const documentNavigationCall = getDocumentNavigation({ document }); - expect(documentNavigationCall.name).toBe('getDocumentNavigation'); - }); - - it('should return correct payload', () => { - const document = GuidelineDocumentClassDummy.with(101, 'document-101'); - - const documentNavigationCall = getDocumentNavigation({ document }); - expect(documentNavigationCall.payload.document).toBeInstanceOf(GuidelineDocumentClass); - }); -}); diff --git a/packages/app-bridge/src/registries/api/GetDocumentNavigation.ts b/packages/app-bridge/src/registries/api/GetDocumentNavigation.ts deleted file mode 100644 index fbaa8465f..000000000 --- a/packages/app-bridge/src/registries/api/GetDocumentNavigation.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type DocumentNavigationItem, type GuidelineDocument } from '../../types/Guideline'; - -export type GetDocumentNavigationPayload = { - document: GuidelineDocument; -}; - -export type GetDocumentNavigationResponse = DocumentNavigationItem[]; - -export const getDocumentNavigation = ( - payload: GetDocumentNavigationPayload, -): { name: 'getDocumentNavigation'; payload: GetDocumentNavigationPayload } => ({ - name: 'getDocumentNavigation', - payload, -}); diff --git a/packages/app-bridge/src/registries/api/GetPortalNavigation.spec.ts b/packages/app-bridge/src/registries/api/GetPortalNavigation.spec.ts deleted file mode 100644 index 77bf9df8b..000000000 --- a/packages/app-bridge/src/registries/api/GetPortalNavigation.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { getPortalNavigation } from './GetPortalNavigation'; - -describe('GetPortalNavigation', () => { - it('should return correct method name', () => { - const portalNavigationCall = getPortalNavigation(); - expect(portalNavigationCall.name).toBe('getPortalNavigation'); - }); -}); diff --git a/packages/app-bridge/src/registries/api/GetPortalNavigation.ts b/packages/app-bridge/src/registries/api/GetPortalNavigation.ts deleted file mode 100644 index a8ae0b80a..000000000 --- a/packages/app-bridge/src/registries/api/GetPortalNavigation.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type PortalNavigationItem } from '../../types/Guideline'; - -export type GetPortalNavigationResponse = PortalNavigationItem[]; - -export const getPortalNavigation = () => ({ name: 'getPortalNavigation' }); diff --git a/packages/app-bridge/src/registries/api/GetProxyNetworkCall.spec.ts b/packages/app-bridge/src/registries/api/GetProxyNetworkCall.spec.ts deleted file mode 100644 index 1d6ce37d0..000000000 --- a/packages/app-bridge/src/registries/api/GetProxyNetworkCall.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { getProxyNetworkCall } from './GetProxyNetworkCall'; - -describe('GetProxyNetworkCall', () => { - it('should return correct method name', () => { - const TEST_ID = 'user-api'; - const proxyNetworkCall = getProxyNetworkCall({ id: TEST_ID, props: 'data' }); - expect(proxyNetworkCall.name).toBe('getProxyNetworkCall'); - expect(proxyNetworkCall.payload).toStrictEqual({ - id: TEST_ID, - props: 'data', - }); - }); -}); diff --git a/packages/app-bridge/src/registries/api/GetProxyNetworkCall.ts b/packages/app-bridge/src/registries/api/GetProxyNetworkCall.ts deleted file mode 100644 index c0e800190..000000000 --- a/packages/app-bridge/src/registries/api/GetProxyNetworkCall.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type GetProxyNetworkCallPayload = { - id: string; - props: unknown; -}; - -export type GetProxyNetworkCallResponse = { - data: unknown; -}; - -export const getProxyNetworkCall = (payload: GetProxyNetworkCallPayload) => ({ name: 'getProxyNetworkCall', payload }); diff --git a/packages/app-bridge/src/registries/api/index.ts b/packages/app-bridge/src/registries/api/index.ts index 4ed17f00d..cb8368d33 100644 --- a/packages/app-bridge/src/registries/api/index.ts +++ b/packages/app-bridge/src/registries/api/index.ts @@ -2,10 +2,5 @@ export * from './ApiMethodRegistry'; export * from './GetAssetBulkDownloadToken'; -export * from './CreateAsset'; -export * from './GetAssetResourceInformation'; -export * from './GetCurrentUser'; export * from './SetAssetIdsByBlockAssetKey'; -export * from './GetProxyNetworkCall'; -export * from './GetDocumentNavigation'; -export * from './GetPortalNavigation'; +export * from './GetCurrentUser'; diff --git a/packages/app-bridge/src/registries/commands/CommandRegistry.ts b/packages/app-bridge/src/registries/commands/CommandRegistry.ts index d3e1443a3..9fc869da8 100644 --- a/packages/app-bridge/src/registries/commands/CommandRegistry.ts +++ b/packages/app-bridge/src/registries/commands/CommandRegistry.ts @@ -13,11 +13,9 @@ type CloseAssetChooserPayload = void; type OpenAssetViewerPayload = AssetViewerOptions; type OpenTemplateChooser = void; type CloseTemplateChooser = void; -type OpenNavigationManager = void; type DownloadAsset = Asset; type OpenSearchDialog = void; type CloseSearchDialog = void; -type NavigateToDocumentSection = number | string; export type CommandRegistry = CommandNameValidator<{ openAssetChooser?: OpenAssetChooserPayload; @@ -26,9 +24,7 @@ export type CommandRegistry = CommandNameValidator<{ openTemplateChooser: OpenTemplateChooser; closeTemplateChooser: CloseTemplateChooser; downloadAsset: DownloadAsset; - openNavigationManager: OpenNavigationManager; openNewPublication: OpenNewPublicationPayload; openSearchDialog: OpenSearchDialog; closeSearchDialog: CloseSearchDialog; - navigateToDocumentSection: NavigateToDocumentSection; }>; diff --git a/packages/app-bridge/src/registries/commands/Connection.ts b/packages/app-bridge/src/registries/commands/Connection.ts deleted file mode 100644 index 89919daa8..000000000 --- a/packages/app-bridge/src/registries/commands/Connection.ts +++ /dev/null @@ -1,3 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export const openConnection = (): { name: 'openConnection' } => ({ name: 'openConnection' }); diff --git a/packages/app-bridge/src/registries/commands/NavigationManager.ts b/packages/app-bridge/src/registries/commands/NavigationManager.ts deleted file mode 100644 index efe190276..000000000 --- a/packages/app-bridge/src/registries/commands/NavigationManager.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type DispatchHandlerParameter } from '../../AppBridge'; - -import { type CommandRegistry } from './CommandRegistry'; - -export const openNavigationManager = (): DispatchHandlerParameter<'openNavigationManager', CommandRegistry> => ({ - name: 'openNavigationManager', -}); diff --git a/packages/app-bridge/src/registries/commands/index.ts b/packages/app-bridge/src/registries/commands/index.ts index d1e78f8e6..d98b80338 100644 --- a/packages/app-bridge/src/registries/commands/index.ts +++ b/packages/app-bridge/src/registries/commands/index.ts @@ -3,10 +3,7 @@ export * from './AssetChooser'; export * from './AssetViewer'; export * from './CommandRegistry'; -export * from './Connection'; export * from './DownloadAsset'; -export * from './NavigationManager'; export * from './NewPublication'; export * from './SearchDialog'; export * from './TemplateChooser'; -export * from './NavigateToDocumentSection'; diff --git a/packages/app-bridge/src/registries/verbs/Verbs.ts b/packages/app-bridge/src/registries/verbs/Verbs.ts index a18d6e7b8..6881c0938 100644 --- a/packages/app-bridge/src/registries/verbs/Verbs.ts +++ b/packages/app-bridge/src/registries/verbs/Verbs.ts @@ -1,5 +1,5 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ export type ApiVerb = 'get' | 'create' | 'update' | 'delete' | 'add' | 'remove' | 'set'; -export type CommandVerb = 'open' | 'close' | 'navigate' | 'download'; +export type CommandVerb = 'open' | 'close' | 'download'; export type EventVerb = 'chosen'; diff --git a/packages/app-bridge/src/repositories/AssetRepository.ts b/packages/app-bridge/src/repositories/AssetRepository.ts index f1f16ad03..0ee863fa6 100644 --- a/packages/app-bridge/src/repositories/AssetRepository.ts +++ b/packages/app-bridge/src/repositories/AssetRepository.ts @@ -16,6 +16,7 @@ export const createAssetByFileId = async (fileId: string, projectId: number, set export const mapAssetApiToAsset = (asset: AssetApi): Asset => { return { id: asset.id, + alternativeText: asset.alternative_text, objectType: asset.object_type, extension: asset.ext, creatorName: asset.creator_name, diff --git a/packages/app-bridge/src/tests/AppBridgeBlockStub.ts b/packages/app-bridge/src/tests/AppBridgeBlockStub.ts index 7829119f7..3541da34a 100644 --- a/packages/app-bridge/src/tests/AppBridgeBlockStub.ts +++ b/packages/app-bridge/src/tests/AppBridgeBlockStub.ts @@ -14,6 +14,9 @@ import { AssetDummy } from './AssetDummy'; import { BulkDownloadDummy } from './BulkDownloadDummy'; import { ColorDummy } from './ColorDummy'; import { ColorPaletteDummy } from './ColorPaletteDummy'; +import { DocumentCategoryDummy } from './DocumentCategoryDummy'; +import { DocumentDummy } from './DocumentDummy'; +import { DocumentPageDummy } from './DocumentPageDummy'; import { DocumentSectionApiDummy } from './DocumentSectionApiDummy'; import { TemplateDummy } from './TemplateDummy'; import { TemplateLegacyDummy } from './TemplateLegacyDummy'; @@ -24,12 +27,26 @@ const SECTION_ID = 2341; const USER_ID = 4561; const PROJECT_ID = 345214; +const DOCUMENT_GROUP_ID_1 = 5332; +const GROUPED_DOCUMENT_ID_1 = 2434; +const GROUPED_DOCUMENT_ID_2 = 552; +const GROUPED_DOCUMENT_ID_3 = 1145; +const GROUPED_DOCUMENT_ID_4 = 32345; +const DOCUMENT_PAGE_ID_1 = 23442; +const DOCUMENT_PAGE_ID_2 = 235345; +const DOCUMENT_PAGE_ID_3 = 12352; +const DOCUMENT_PAGE_ID_4 = 55221; +const UNCATEGORIZED_DOCUMENT_PAGE_ID_1 = 24324; +const UNCATEGORIZED_DOCUMENT_PAGE_ID_2 = 3532; +const UNCATEGORIZED_DOCUMENT_PAGE_ID_3 = 98954; +const DOCUMENT_CATEGORY_ID_1 = 147; +const DOCUMENT_CATEGORY_ID_2 = 258; +const DOCUMENT_CATEGORY_ID_3 = 369; + export type getAppBridgeBlockStubProps = { blockSettings?: Record; blockAssets?: Record; editorState?: boolean; - openAssetChooser?: (callback: Parameters[0]) => void; - closeAssetChooser?: () => void; blockId?: number; sectionId?: number; projectId?: number; @@ -46,8 +63,6 @@ export const getAppBridgeBlockStub = ({ blockSettings = {}, blockAssets = {}, editorState = false, - openAssetChooser = () => null, - closeAssetChooser = () => null, blockId = BLOCK_ID, sectionId = SECTION_ID, projectId = PROJECT_ID, @@ -73,16 +88,9 @@ export const getAppBridgeBlockStub = ({ const addedTemplateIds: Record = {}; return { - getBlockId: stub>().returns(blockId), - getSectionId: stub>().returns(sectionId), getProjectId: stub>().returns(projectId), getEditorState: stub>().returns(editorState), getBlockSettings: stub>().resolves(window.blockSettings), - getAvailablePalettes: stub>().resolves([ - ColorPaletteDummy.with(678, 'Palette 1'), - ColorPaletteDummy.with(427, 'Palette 2'), - ColorPaletteDummy.with(679, 'Palette 3'), - ]), getColorPalettes: stub>().resolves([ ColorPaletteDummy.with(678, 'Palette 1'), ColorPaletteDummy.with(427, 'Palette 2'), @@ -110,7 +118,6 @@ export const getAppBridgeBlockStub = ({ ColorPaletteDummy.with(679, 'Palette 3'), ]), deleteColorPalette: stub>().resolves(), - getAvailableColors: stub>().resolves([]), getCurrentLoggedUser: stub>().resolves(user), downloadColorKit: stub>().returns( `/api/color/export/${PROJECT_ID}/zip/500`, @@ -118,12 +125,6 @@ export const getAppBridgeBlockStub = ({ getAssetById: stub>().callsFake((assetId) => Promise.resolve(AssetDummy.with(assetId)), ), - closeAssetChooser: stub>().callsFake(() => { - closeAssetChooser(); - }), - openAssetChooser: stub>().callsFake((callback) => { - openAssetChooser(callback); - }), getBlockAssets: stub>().callsFake(async () => { return Object.entries(blockAssets).reduce>((assetsDiff, [key, assets]) => { const addedAssetIdsList = addedAssetIds[key] ?? []; @@ -247,31 +248,70 @@ export const getAppBridgeBlockStub = ({ getDocumentSectionsByDocumentPageId: stub< Parameters >().resolves([ - DocumentSectionApiDummy.withFields({ id: 1, title: 'Title 1' }), - DocumentSectionApiDummy.withFields({ id: 2, title: 'Title 2' }), + DocumentSectionApiDummy.withFields({ id: 1, title: null }), + DocumentSectionApiDummy.withFields({ id: 2, title: 'Title' }), DocumentSectionApiDummy.withFields({ id: 3, title: ' ' }), DocumentSectionApiDummy.withFields({ id: 4, title: '' }), ]), // TODO: Stub the following methods - closeTemplateChooser: stub>(), - openTemplateChooser: stub>(), createColor: stub>().resolves(ColorDummy.red()), deleteColor: stub>().resolves(), getTemplateById: stub>().resolves({} as TemplateLegacy), - openAssetViewer: stub>(), updateBlockSettings: stub>().resolves(), getAllDocuments: stub>().resolves(), getUngroupedDocuments: stub>().resolves(), - getDocumentsByDocumentGroupId: stub>().resolves(), + getDocumentsByDocumentGroupId: stub>().resolves([ + DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_1, DOCUMENT_GROUP_ID_1), + DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_2, DOCUMENT_GROUP_ID_1), + DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_3, DOCUMENT_GROUP_ID_1), + DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_4, DOCUMENT_GROUP_ID_1), + ]), getDocumentGroups: stub>().resolves(), getDocumentPagesByDocumentId: stub>().resolves(), - getDocumentPagesByDocumentCategoryId: - stub>().resolves(), - getDocumentCategoriesByDocumentId: - stub>().resolves(), - getUncategorizedDocumentPagesByDocumentId: - stub>().resolves(), + getDocumentPagesByDocumentCategoryId: stub< + Parameters + >().callsFake((documentCategoryId) => + Promise.resolve([ + DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_1, categoryId: documentCategoryId, sort: 1 }), + DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_2, categoryId: documentCategoryId, sort: 2 }), + DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_3, categoryId: documentCategoryId, sort: 3 }), + DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_4, categoryId: documentCategoryId, sort: 4 }), + ]), + ), + getDocumentCategoriesByDocumentId: stub< + Parameters + >().callsFake((documentId) => + Promise.resolve([ + DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_1, documentId, 2), + DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_2, documentId, 0), + DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_3, documentId, 2), + ]), + ), + getUncategorizedDocumentPagesByDocumentId: stub< + Parameters + >().callsFake((documentId) => + Promise.resolve([ + DocumentPageDummy.withFields({ + id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, + documentId, + categoryId: null, + sort: 1, + }), + DocumentPageDummy.withFields({ + id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, + documentId, + categoryId: null, + sort: 2, + }), + DocumentPageDummy.withFields({ + id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, + documentId, + categoryId: null, + sort: 3, + }), + ]), + ), getDocumentTargets: stub>().resolves(), getDocumentPageTargets: stub>().resolves(), state: stub>().resolves(), diff --git a/packages/app-bridge/src/tests/AppBridgePlatformAppStub.ts b/packages/app-bridge/src/tests/AppBridgePlatformAppStub.ts deleted file mode 100644 index c2c3cf5b0..000000000 --- a/packages/app-bridge/src/tests/AppBridgePlatformAppStub.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type SinonStubbedInstance, stub } from 'sinon'; - -import { type IAppBridgePlatformApp } from '../AppBridgePlatformApp'; - -export type getAppBridgePlatformAppStubProps = { - // -}; - -export const getAppBridgePlatformAppStub = (): SinonStubbedInstance => { - return { - api: stub>().resolves(), - state: stub>().resolves(), - context: stub>().resolves(), - subscribe: stub>().resolves(), - dispatch: stub>().resolves(), - }; -}; diff --git a/packages/app-bridge/src/tests/AppBridgeThemeStub.ts b/packages/app-bridge/src/tests/AppBridgeThemeStub.ts deleted file mode 100644 index b04cd8a36..000000000 --- a/packages/app-bridge/src/tests/AppBridgeThemeStub.ts +++ /dev/null @@ -1,387 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import mitt, { type Emitter } from 'mitt'; -import { type SinonStubbedInstance, spy, stub } from 'sinon'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; -import { type Asset, type ThemeTemplate } from '../types'; -import { type EmitterEvents } from '../types/Emitter'; -import { mergeDeep } from '../utilities'; - -import { GuidelineSearchResultDummy } from './GuidelineSearchResultDummy'; - -import { - AssetDummy, - BrandportalLinkDummy, - ColorDummy, - ColorPaletteDummy, - CoverPageDummy, - DocumentCategoryDummy, - DocumentDummy, - DocumentGroupDummy, - DocumentPageDummy, - DocumentPageTargetsDummy, - DocumentSectionDummy, - DocumentTargetsDummy, - UpdateTargetsDummy, -} from '.'; - -const BRAND_ID = 234551; -const PROJECT_ID = 3452; -const PORTAL_ID = 7777; -const DOCUMENT_GROUP_ID_1 = 5332; -const DOCUMENT_GROUP_ID_2 = 95694; -const DOCUMENT_GROUP_ID_3 = 345882; -const DOCUMENT_ID_1 = 6456; -const DOCUMENT_ID_2 = 34532; -const DOCUMENT_ID_3 = 3455345; -const DOCUMENT_ID_4 = 2342; -const DOCUMENT_ID_5 = 2343445; -const GROUPED_DOCUMENT_ID_1 = 2434; -const GROUPED_DOCUMENT_ID_2 = 552; -const GROUPED_DOCUMENT_ID_3 = 1145; -const GROUPED_DOCUMENT_ID_4 = 32345; -const DOCUMENT_CATEGORY_ID_1 = 147; -const DOCUMENT_CATEGORY_ID_2 = 258; -const DOCUMENT_CATEGORY_ID_3 = 369; -const DOCUMENT_PAGE_ID_1 = 23442; -const DOCUMENT_PAGE_ID_2 = 235345; -const DOCUMENT_PAGE_ID_3 = 12352; -const DOCUMENT_PAGE_ID_4 = 55221; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_1 = 24324; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_2 = 3532; -const UNCATEGORIZED_DOCUMENT_PAGE_ID_3 = 98954; -const DOCUMENT_SECTION_ID_1 = 3421; -const DOCUMENT_SECTION_ID_2 = 65725; -const DOCUMENT_SECTION_ID_3 = 95934; -const DOCUMENT_PAGE_DUPLICATE_ID_1 = 2341; -const TARGET_ID_1 = 653543; -const TARGET_ID_2 = 23411; -const TARGET_ID_3 = 56657; - -export type getAppBridgeThemeStubProps = { - editorState?: boolean; - brandId?: number; - portalId?: number; - projectId?: number; - pageTemplateSettings?: Record; - pageTemplateAssets?: Record; - themeSettings?: Record>; - themeAssets?: Record; - language?: string; - openAssetChooser?: (callback: Parameters[0]) => void; - closeAssetChooser?: () => void; -}; - -export const getAppBridgeThemeStub = ({ - editorState = false, - brandId = BRAND_ID, - portalId = PORTAL_ID, - projectId = PROJECT_ID, - pageTemplateSettings = {}, - pageTemplateAssets = {}, - themeSettings = { cover: {}, documentPage: {}, library: {} }, - themeAssets = {}, - language = 'en', - openAssetChooser = () => null, - closeAssetChooser = () => null, -}: getAppBridgeThemeStubProps = {}): SinonStubbedInstance => { - window.emitter = spy(mitt()) as unknown as Emitter; - - let localPageTemplateSettings = pageTemplateSettings; - let localThemeSettings = themeSettings; - - const deletedAssetIds: Record = {}; - const addedAssetIds: Record = {}; - - const getAssets = async (themeOrTemplateAssets: Record) => { - return Object.entries(themeOrTemplateAssets).reduce>((assetsDiff, [key, assets]) => { - const addedAssetIdsList = addedAssetIds[key] ?? []; - const deletedAssetIdsList = deletedAssetIds[key] ?? []; - assetsDiff[key] = [ - ...assets.filter((asset) => !deletedAssetIdsList.includes(asset.id)), - ...addedAssetIdsList.map((id) => AssetDummy.with(id)), - ]; - return assetsDiff; - }, {}); - }; - - const getTemplateAssets = async () => getAssets(pageTemplateAssets); - - const getThemeAssets = async () => getAssets(themeAssets); - - return { - getPortalId: stub>().returns(portalId), - getProjectId: stub>().returns(projectId), - getEditorState: stub>().returns(editorState), - getCoverPage: stub>().resolves(CoverPageDummy.with(PORTAL_ID)), - getAllDocuments: stub>().resolves([ - DocumentDummy.with(DOCUMENT_ID_1), - DocumentDummy.with(DOCUMENT_ID_2), - DocumentDummy.with(DOCUMENT_ID_3), - DocumentDummy.with(DOCUMENT_ID_4), - DocumentDummy.with(DOCUMENT_ID_5), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_1, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_2, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_3, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_4, DOCUMENT_GROUP_ID_1), - ]), - getUngroupedDocuments: stub>().resolves([ - DocumentDummy.with(DOCUMENT_ID_1), - DocumentDummy.with(DOCUMENT_ID_2), - DocumentDummy.with(DOCUMENT_ID_3), - DocumentDummy.with(DOCUMENT_ID_4), - DocumentDummy.with(DOCUMENT_ID_5), - ]), - getDocumentsByDocumentGroupId: stub>().resolves([ - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_1, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_2, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_3, DOCUMENT_GROUP_ID_1), - DocumentDummy.withDocumentGroupId(GROUPED_DOCUMENT_ID_4, DOCUMENT_GROUP_ID_1), - ]), - getDocumentGroups: stub>().resolves([ - DocumentGroupDummy.with(DOCUMENT_GROUP_ID_1, 3), - DocumentGroupDummy.with(DOCUMENT_GROUP_ID_2, 0), - DocumentGroupDummy.with(DOCUMENT_GROUP_ID_3, 2), - ]), - getDocumentPagesByDocumentId: stub>().resolves([ - DocumentPageDummy.with(DOCUMENT_PAGE_ID_1), - DocumentPageDummy.with(DOCUMENT_PAGE_ID_2), - DocumentPageDummy.with(UNCATEGORIZED_DOCUMENT_PAGE_ID_1), - DocumentPageDummy.with(DOCUMENT_PAGE_ID_3), - DocumentPageDummy.with(UNCATEGORIZED_DOCUMENT_PAGE_ID_2), - DocumentPageDummy.with(DOCUMENT_PAGE_ID_4), - DocumentPageDummy.with(UNCATEGORIZED_DOCUMENT_PAGE_ID_3), - ]), - getDocumentPagesByDocumentCategoryId: stub< - Parameters - >().callsFake((documentCategoryId) => - Promise.resolve([ - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_1, categoryId: documentCategoryId, sort: 1 }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_2, categoryId: documentCategoryId, sort: 2 }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_3, categoryId: documentCategoryId, sort: 3 }), - DocumentPageDummy.withFields({ id: DOCUMENT_PAGE_ID_4, categoryId: documentCategoryId, sort: 4 }), - ]), - ), - getDocumentCategoriesByDocumentId: stub< - Parameters - >().callsFake((documentId) => - Promise.resolve([ - DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_1, documentId, 2), - DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_2, documentId, 0), - DocumentCategoryDummy.withDocumentIdAndNumberOfDocumentPages(DOCUMENT_CATEGORY_ID_3, documentId, 2), - ]), - ), - getUncategorizedDocumentPagesByDocumentId: stub< - Parameters - >().callsFake((documentId) => - Promise.resolve([ - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_1, - documentId, - categoryId: null, - sort: 1, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_2, - documentId, - categoryId: null, - sort: 2, - }), - DocumentPageDummy.withFields({ - id: UNCATEGORIZED_DOCUMENT_PAGE_ID_3, - documentId, - categoryId: null, - sort: 3, - }), - ]), - ), - getDocumentSectionsByDocumentPageId: stub< - Parameters - >().resolves([ - DocumentSectionDummy.with(DOCUMENT_SECTION_ID_1), - DocumentSectionDummy.with(DOCUMENT_SECTION_ID_2), - DocumentSectionDummy.with(DOCUMENT_SECTION_ID_3), - ]), - getColorPalettes: stub>().resolves([ - ColorPaletteDummy.with(678, 'Palette 1'), - ColorPaletteDummy.with(427, 'Palette 2'), - ColorPaletteDummy.with(679, 'Palette 3'), - ]), - getColorsByColorPaletteId: stub>().resolves([ - ColorDummy.red(9834), - ColorDummy.green(342), - ColorDummy.yellow(9314), - ]), - duplicateDocumentPage: stub>().resolves( - DocumentPageDummy.with(DOCUMENT_PAGE_DUPLICATE_ID_1), - ), - getDocumentTargets: stub>().resolves( - DocumentTargetsDummy.with(DOCUMENT_ID_1), - ), - getDocumentPageTargets: stub>().resolves( - DocumentPageTargetsDummy.with(DOCUMENT_PAGE_ID_1), - ), - addAssetIdsToCoverPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (key: string, assetsIds: number[]) => { - addedAssetIds[key] = [...(addedAssetIds[key] ?? []), ...assetsIds]; - return pageTemplateAssets; - }), - addAssetIdsToThemeAssetKey: stub>().callsFake( - async (key: string, assetsIds: number[]) => { - addedAssetIds[key] = [...(addedAssetIds[key] ?? []), ...assetsIds]; - return pageTemplateAssets; - }, - ), - addAssetIdsToLibraryPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (_documentId: number, key: string, assetsIds: number[]) => { - addedAssetIds[key] = [...(addedAssetIds[key] ?? []), ...assetsIds]; - return pageTemplateAssets; - }), - addAssetIdsToDocumentPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (_documentPageId: number, key: string, assetsIds: number[]) => { - addedAssetIds[key] = [...(addedAssetIds[key] ?? []), ...assetsIds]; - return pageTemplateAssets; - }), - getCoverPageTemplateAssets: - stub>().callsFake(getTemplateAssets), - getThemeAssets: stub>().callsFake(getThemeAssets), - getLibraryPageTemplateAssets: - stub>().callsFake(getTemplateAssets), - getDocumentPageTemplateAssets: - stub>().callsFake(getTemplateAssets), - deleteAssetIdsFromCoverPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (key, assetIds) => { - deletedAssetIds[key] = [...(deletedAssetIds[key] ?? []), ...assetIds]; - }), - deleteAssetIdsFromThemeAssetKey: stub< - Parameters - >().callsFake(async (key, assetIds) => { - deletedAssetIds[key] = [...(deletedAssetIds[key] ?? []), ...assetIds]; - }), - deleteAssetIdsFromLibraryPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (_documentId: number, key, assetIds) => { - deletedAssetIds[key] = [...(deletedAssetIds[key] ?? []), ...assetIds]; - }), - deleteAssetIdsFromDocumentPageTemplateAssetKey: stub< - Parameters - >().callsFake(async (_documentPageId: number, key, assetIds) => { - deletedAssetIds[key] = [...(deletedAssetIds[key] ?? []), ...assetIds]; - }), - getCoverPageTemplateSettings: - stub>().resolves(localPageTemplateSettings), - getDocumentPageTemplateSettings: - stub>().resolves(localPageTemplateSettings), - getLibraryPageTemplateSettings: - stub>().resolves(localPageTemplateSettings), - getThemeSettings: stub>().resolves(localThemeSettings), - createLink: stub>().resolves(DocumentDummy.with(1)), - createLibrary: stub>().resolves(DocumentDummy.with(1)), - createStandardDocument: stub>().resolves( - DocumentDummy.with(1), - ), - createDocumentPage: stub>().resolves( - DocumentPageDummy.with(1), - ), - createDocumentGroup: stub>().resolves( - DocumentGroupDummy.with(1, 0), - ), - createDocumentCategory: stub>().resolves( - DocumentCategoryDummy.with(1), - ), - createCoverPage: stub>().resolves(CoverPageDummy.with(1)), - updateLink: stub>().resolves(DocumentDummy.with(1)), - updateLibrary: stub>().resolves(DocumentDummy.with(1)), - updateStandardDocument: stub>().resolves( - DocumentDummy.with(1), - ), - updateDocumentPage: stub>().resolves( - DocumentPageDummy.with(1), - ), - updateDocumentGroup: stub>().resolves( - DocumentGroupDummy.with(1, 0), - ), - updateDocumentCategory: stub>().resolves( - DocumentCategoryDummy.with(1), - ), - updateCoverPage: stub>().resolves(CoverPageDummy.with(1)), - updateLegacyCoverPage: stub>().resolves( - CoverPageDummy.withLegacy(1), - ), - updateBrandportalLink: stub>().resolves( - BrandportalLinkDummy.with(), - ), - updateCoverPageTemplateSettings: stub< - Parameters - >().callsFake(async (pageTemplateSettingsUpdate) => { - localPageTemplateSettings = mergeDeep(localPageTemplateSettings, pageTemplateSettingsUpdate); - }), - updateDocumentPageTemplateSettings: stub< - Parameters - >().callsFake(async (pageTemplateSettingsUpdate) => { - localPageTemplateSettings = mergeDeep(localPageTemplateSettings, pageTemplateSettingsUpdate); - }), - updateLibraryPageTemplateSettings: stub< - Parameters - >().callsFake(async (pageTemplateSettingsUpdate) => { - localPageTemplateSettings = mergeDeep(localPageTemplateSettings, pageTemplateSettingsUpdate); - }), - updateThemeSettings: stub>().callsFake( - async (themeSettingsUpdate) => { - localThemeSettings = mergeDeep(localThemeSettings, themeSettingsUpdate); - }, - ), - deleteCoverPage: stub>().resolves(), - deleteDocumentCategory: stub>().resolves(), - deleteDocumentGroup: stub>().resolves(), - deleteDocumentPage: stub>().resolves(), - deleteLibrary: stub>().resolves(), - deleteLink: stub>().resolves(), - deleteStandardDocument: stub>().resolves(), - getBrandId: stub>().returns(brandId), - getBrandportalLink: stub>().resolves( - BrandportalLinkDummy.with(), - ), - getTranslationLanguage: stub>().returns(language), - moveDocument: stub>().resolves(DocumentDummy.with(DOCUMENT_ID_1)), - moveDocumentCategory: stub>().resolves( - DocumentCategoryDummy.with(DOCUMENT_CATEGORY_ID_1), - ), - moveDocumentGroup: stub>().resolves( - DocumentGroupDummy.with(DOCUMENT_CATEGORY_ID_1, 0), - ), - moveDocumentPage: stub>().resolves( - DocumentPageDummy.with(DOCUMENT_PAGE_ID_1), - ), - openNavigationManager: stub>(), - updateDocumentPageTargets: stub>().resolves( - UpdateTargetsDummy.with([TARGET_ID_1, TARGET_ID_2, TARGET_ID_3]), - ), - updateDocumentTargets: stub>().resolves( - UpdateTargetsDummy.with([TARGET_ID_1, TARGET_ID_2, TARGET_ID_3]), - ), - searchInGuideline: stub>().callsFake(async (query) => { - return Promise.resolve([ - GuidelineSearchResultDummy.with(`${query}-1`), - GuidelineSearchResultDummy.with(`${query}-1`), - GuidelineSearchResultDummy.with(`${query}-3`), - ]); - }), - closeAssetChooser: stub>().callsFake(() => { - closeAssetChooser(); - }), - openAssetChooser: stub>().callsFake((callback) => { - openAssetChooser(callback); - }), - api: stub>().resolves(), - state: stub>().resolves(), - context: stub>().resolves(), - subscribe: stub>().resolves(), - dispatch: stub>().resolves(), - }; -}; diff --git a/packages/app-bridge/src/tests/AssetApiDummy.ts b/packages/app-bridge/src/tests/AssetApiDummy.ts index b4f37197a..8d50abf43 100644 --- a/packages/app-bridge/src/tests/AssetApiDummy.ts +++ b/packages/app-bridge/src/tests/AssetApiDummy.ts @@ -7,6 +7,7 @@ export class AssetApiDummy { return { id, creator_name: 'Creator Name', + alternative_text: 'Alternative Text', ext: 'png', external_url: null, file_id: 'x1x1x1x1x1x1', diff --git a/packages/app-bridge/src/tests/AssetChooserResultDummy.ts b/packages/app-bridge/src/tests/AssetChooserResultDummy.ts index 3df361c64..fabe32a57 100644 --- a/packages/app-bridge/src/tests/AssetChooserResultDummy.ts +++ b/packages/app-bridge/src/tests/AssetChooserResultDummy.ts @@ -7,6 +7,7 @@ export class AssetChooserResultDummy { return { id, creator_name: 'Creator Name', + computed_alternative_text: 'Alternative Text', ext: 'png', external_url: null, file_id: 'x1x1x1x1x1x1', diff --git a/packages/app-bridge/src/tests/AssetDummy.ts b/packages/app-bridge/src/tests/AssetDummy.ts index e7e55ad0c..7f25d37c4 100644 --- a/packages/app-bridge/src/tests/AssetDummy.ts +++ b/packages/app-bridge/src/tests/AssetDummy.ts @@ -7,6 +7,7 @@ export class AssetDummy { return { id, creatorName: 'Creator Name', + alternativeText: 'Alternative Text', extension: 'png', externalUrl: null, fileName: 'fileName.png', diff --git a/packages/app-bridge/src/tests/BrandportalLinkApiDummy.ts b/packages/app-bridge/src/tests/BrandportalLinkApiDummy.ts deleted file mode 100644 index 77079584a..000000000 --- a/packages/app-bridge/src/tests/BrandportalLinkApiDummy.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type BrandportalLinkApi } from '../types'; - -export class BrandportalLinkApiDummy { - static get(): BrandportalLinkApi { - return { - brand_portal: { - enabled: true, - label: 'Brandportal Link', - url: '/url', - }, - }; - } -} diff --git a/packages/app-bridge/src/tests/BrandportalLinkDummy.ts b/packages/app-bridge/src/tests/BrandportalLinkDummy.ts deleted file mode 100644 index adf7bcc2b..000000000 --- a/packages/app-bridge/src/tests/BrandportalLinkDummy.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type BrandportalLink } from '../types'; -import { convertObjectCase } from '../utilities'; - -import { BrandportalLinkApiDummy } from './BrandportalLinkApiDummy'; - -export class BrandportalLinkDummy { - static with(fields?: Partial): BrandportalLink { - return { ...convertObjectCase(BrandportalLinkApiDummy.get().brand_portal, 'camel'), ...fields }; - } -} diff --git a/packages/app-bridge/src/tests/ColorDummy.ts b/packages/app-bridge/src/tests/ColorDummy.ts index 095feb433..f6e78b4b9 100644 --- a/packages/app-bridge/src/tests/ColorDummy.ts +++ b/packages/app-bridge/src/tests/ColorDummy.ts @@ -6,108 +6,127 @@ export class ColorDummy { static red(id = 767): Color { return { id, - name: 'Red', sort: 1, - nameCss: 'red', - hex: 'ff0000', - red: 255, - green: 0, - blue: 0, - alpha: 255, - hue: 361, - saturation: 100, - lightness: 46, - c: 0, - m: 100, - y: 100, - k: 0, - pantone: null, - ral: null, - oracal: null, - pantoneCoated: null, - pantoneUncoated: null, - cmykCoated: null, - cmykUncoated: null, - cmykNewspaper: null, - ncs: null, - pantoneCp: null, - pantonePlastics: null, - pantoneTextile: null, - hks: null, - threeM: null, - lab: null, + title: 'Red', + revision: { + nameCss: 'red', + hex: 'ff0000', + hue: 361, + saturation: 100, + lightness: 46, + rgba: { red: 255, green: 0, blue: 0, alpha: 255 }, + cmyk: { + cyan: 0, + magenta: 100, + yellow: 100, + black: 0, + coated: null, + uncoated: null, + newspaper: null, + }, + pantone: { + code: null, + coated: null, + uncoated: null, + cp: null, + plastics: null, + textile: null, + }, + ral: null, + oracal: null, + ncs: null, + hks: null, + threeM: null, + lab: null, + }, }; } static yellow(id = 768): Color { return { id, - name: 'Yellow', sort: 2, - nameCss: 'yellow', - red: 255, - green: 255, - blue: 0, - hex: 'ffff00', - alpha: 255, - hue: 361, - saturation: 100, - lightness: 46, - c: 0, - m: 0, - y: 100, - k: 0, - pantone: null, - ral: null, - oracal: null, - pantoneCoated: null, - pantoneUncoated: null, - cmykCoated: null, - cmykUncoated: null, - cmykNewspaper: null, - ncs: null, - pantoneCp: null, - pantonePlastics: null, - pantoneTextile: null, - hks: null, - threeM: null, - lab: null, + title: 'Yellow', + revision: { + nameCss: 'yellow', + hex: 'ffff00', + hue: 361, + saturation: 100, + lightness: 46, + ral: null, + oracal: null, + ncs: null, + hks: null, + threeM: null, + lab: null, + rgba: { + red: 255, + green: 255, + blue: 0, + alpha: 255, + }, + cmyk: { + cyan: 0, + magenta: 0, + yellow: 100, + black: 0, + coated: null, + uncoated: null, + newspaper: null, + }, + pantone: { + code: null, + coated: null, + uncoated: null, + cp: null, + plastics: null, + textile: null, + }, + }, }; } static green(id = 769): Color { return { id, - name: 'Green', sort: 3, - nameCss: 'green', - red: 0, - green: 255, - blue: 0, - hex: '00ff00', - alpha: 255, - hue: 361, - saturation: 100, - lightness: 46, - c: 100, - m: 0, - y: 100, - k: 0, - pantone: null, - ral: null, - oracal: null, - pantoneCoated: null, - pantoneUncoated: null, - cmykCoated: null, - cmykUncoated: null, - cmykNewspaper: null, - ncs: null, - pantoneCp: null, - pantonePlastics: null, - pantoneTextile: null, - hks: null, - threeM: null, - lab: null, + title: 'Green', + revision: { + nameCss: 'green', + hex: '00ff00', + ral: null, + oracal: null, + ncs: null, + hks: null, + threeM: null, + lab: null, + hue: 361, + saturation: 100, + lightness: 46, + rgba: { + red: 0, + green: 255, + blue: 0, + alpha: 255, + }, + cmyk: { + cyan: 100, + magenta: 0, + yellow: 100, + black: 0, + coated: null, + uncoated: null, + newspaper: null, + }, + pantone: { + code: null, + coated: null, + uncoated: null, + cp: null, + plastics: null, + textile: null, + }, + }, }; } } @@ -127,32 +146,38 @@ export class ColorCreateDummy { export class ColorPatchDummy { static red(): ColorPatch { return { - name: 'Red', + title: 'Red', sort: 1, nameCss: 'red', - red: 255, - green: 0, - blue: 0, - alpha: 255, - c: 0, - m: 100, - y: 100, - k: 0, - pantone: null, ral: null, oracal: null, - pantoneCoated: null, - pantoneUncoated: null, - cmykCoated: null, - cmykUncoated: null, - cmykNewspaper: null, ncs: null, - pantoneCp: null, - pantonePlastics: null, - pantoneTextile: null, hks: null, threeM: null, lab: null, + rgba: { + red: 255, + green: 0, + blue: 0, + alpha: 255, + }, + cmyk: { + cyan: 0, + magenta: 100, + yellow: 100, + black: 0, + coated: null, + uncoated: null, + newspaper: null, + }, + pantone: { + code: null, + coated: null, + uncoated: null, + cp: null, + plastics: null, + textile: null, + }, }; } } diff --git a/packages/app-bridge/src/tests/CoverPageDummy.ts b/packages/app-bridge/src/tests/CoverPageDummy.ts index a6d608638..4958f9897 100644 --- a/packages/app-bridge/src/tests/CoverPageDummy.ts +++ b/packages/app-bridge/src/tests/CoverPageDummy.ts @@ -1,10 +1,15 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ -import { type CoverPage, type CoverPageUpdateLegacy } from '../types'; +import { type CoverPage } from '../types'; import { convertObjectCase } from '../utilities'; import { CoverPageApiDummy } from './CoverPageApiDummy'; +type CoverPageUpdateLegacy = { + brandhome_draft?: boolean; + brandhome_title?: string; + brandhome_hide_in_nav?: boolean; +}; export class CoverPageDummy { static with(id: number, title = 'Cover Page Name'): CoverPage { const coverPageApi = CoverPageApiDummy.with(id, title); diff --git a/packages/app-bridge/src/tests/DocumentNavigationTreeDummy.ts b/packages/app-bridge/src/tests/DocumentNavigationTreeDummy.ts deleted file mode 100644 index e976c928c..000000000 --- a/packages/app-bridge/src/tests/DocumentNavigationTreeDummy.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { - type DocumentNavigationItem, - type GuidelineDocumentPageHeading, - type GuidelineDocumentPage, - type GuidelineDocumentPageLink, - type GuidelinePageCategory, -} from '../types'; - -class DocumentPageCategoryDummy implements GuidelinePageCategory { - readonly type = 'page-category' as const; - - readonly #id: number; - readonly #children: (GuidelineDocumentPage | GuidelineDocumentPageLink)[]; - - constructor(id: number, children?: (GuidelineDocumentPage | GuidelineDocumentPageLink)[]) { - this.#id = id; - this.#children = children ?? []; - } - - id(): number { - return this.#id; - } - - title(): string { - return 'Dummy title'; - } - - slug(): string { - return 'dummy slug'; - } - - children(): (GuidelineDocumentPage | GuidelineDocumentPageLink)[] { - return this.#children; - } -} - -class DocumentPageDummy implements GuidelineDocumentPage { - readonly type = 'document-page' as const; - - readonly #id: number; - readonly #headings: GuidelineDocumentPageHeading[]; - - constructor(id: number, headings?: GuidelineDocumentPageHeading[]) { - this.#id = id ?? 1234; - this.#headings = headings ?? []; - } - - id(): number { - return this.#id; - } - - title(): string { - return 'Dummy title'; - } - - slug(): string { - return 'dummy slug'; - } - - url(): string { - return 'dummy url'; - } - - headings(): GuidelineDocumentPageHeading[] { - return this.#headings; - } -} - -class DocumentPageLinkDummy implements GuidelineDocumentPageLink { - readonly type = 'document-page-link' as const; - - readonly #id: number; - - constructor(id: number) { - this.#id = id; - } - - id(): number { - return this.#id; - } - - title(): string { - return 'Dummy title'; - } - - url(): string { - return 'dummy url'; - } -} - -class DocumentPageHeadingDummy implements GuidelineDocumentPageHeading { - readonly type = 'document-page-heading' as const; - - readonly #id; - - constructor(id: number) { - this.#id = id; - } - - id(): number { - return this.#id; - } - - title(): string { - return 'Dummy title'; - } - - slug(): string { - return 'dummy slug'; - } -} - -export class DocumentNavigationTreeDummy { - static default(): DocumentNavigationItem[] { - return [ - new DocumentPageCategoryDummy(1, [ - new DocumentPageDummy(1, [ - new DocumentPageHeadingDummy(1), - new DocumentPageHeadingDummy(2), - new DocumentPageHeadingDummy(3), - ]), - new DocumentPageLinkDummy(2), - ]), - new DocumentPageDummy(3), - ]; - } - - static alternative(): DocumentNavigationItem[] { - return [ - new DocumentPageCategoryDummy(1, [ - new DocumentPageDummy(1, [new DocumentPageHeadingDummy(2), new DocumentPageHeadingDummy(3)]), - new DocumentPageLinkDummy(2), - ]), - new DocumentPageDummy(2, [new DocumentPageHeadingDummy(1)]), - new DocumentPageLinkDummy(6), - ]; - } -} diff --git a/packages/app-bridge/src/tests/GuidelineSearchResultApiDummy.ts b/packages/app-bridge/src/tests/GuidelineSearchResultApiDummy.ts deleted file mode 100644 index b22f5085f..000000000 --- a/packages/app-bridge/src/tests/GuidelineSearchResultApiDummy.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type GuidelineSearchResultApi } from '../types'; - -export class GuidelineSearchResultApiDummy { - static with(query: string): GuidelineSearchResultApi { - return { - highlights: [query], - type: 'BLOCK', - object_id: 1, - page_id: 1, - page_slug: 'page-slug', - page_title: 'Page Title', - page_category_slug: 'page-category-slug', - block_id: 1, - document_id: 1, - document_slug: 'document-slug', - document_title: 'Document Title', - guideline_title: 'Guideline Title', - portal_id: 1, - portal_token: 'portal-token', - section_id: null, - section_slug: null, - section_title: null, - color_hex: undefined, - project_color_id: undefined, - }; - } -} diff --git a/packages/app-bridge/src/tests/GuidelineSearchResultDummy.ts b/packages/app-bridge/src/tests/GuidelineSearchResultDummy.ts deleted file mode 100644 index 822c8b9a4..000000000 --- a/packages/app-bridge/src/tests/GuidelineSearchResultDummy.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type GuidelineSearchResult } from '../types'; -import { convertObjectCase } from '../utilities'; - -import { GuidelineSearchResultApiDummy } from './GuidelineSearchResultApiDummy'; - -export class GuidelineSearchResultDummy { - static with(query: string): GuidelineSearchResult { - return convertObjectCase(GuidelineSearchResultApiDummy.with(query), 'camel'); - } -} diff --git a/packages/app-bridge/src/tests/PortalNavigationTreeDummy.ts b/packages/app-bridge/src/tests/PortalNavigationTreeDummy.ts deleted file mode 100644 index d414ea0f7..000000000 --- a/packages/app-bridge/src/tests/PortalNavigationTreeDummy.ts +++ /dev/null @@ -1,234 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { - type GuidelineDocument, - type GuidelineDocumentGroup, - type GuidelineDocumentLibrary, - type GuidelineDocumentLink, - LinkSettingsDisplay, - LinkSettingsIconPosition, - type GuidelineCoverPage, - type PortalNavigationItem, -} from '../types'; - -class GuidelineCoverPageClass implements GuidelineCoverPage { - readonly type = 'cover-page' as const; - readonly #id: number; - readonly #title: string; - readonly #isPublished: boolean = false; - readonly #isHiddenFromNavigation: boolean = false; - - constructor(id: number, title: string, published: boolean, hiddenFromNavigation: boolean) { - this.#id = id; - this.#title = title; - this.#isPublished = published; - this.#isHiddenFromNavigation = hiddenFromNavigation; - } - - id() { - return this.#id; - } - title(language?: string) { - return `${this.#title} - ${language ?? 'default'}`; - } - isPublished() { - return this.#isPublished ?? false; - } - isHiddenInNavigation() { - return this.#isHiddenFromNavigation ?? false; - } - url(language?: string) { - return `http://domain.com/hub/${language ?? 'default'}/${this.#id}`; - } -} - -class GuidelineDocumentGroupClass implements GuidelineDocumentGroup { - readonly type = 'document-group' as const; - readonly #id: number; - readonly #title: string; - readonly #children: (GuidelineDocument | GuidelineDocumentLibrary | GuidelineDocumentLink)[] = []; - - constructor( - id: number, - title: string, - children: (GuidelineDocument | GuidelineDocumentLibrary | GuidelineDocumentLink)[], - ) { - this.#id = id; - this.#title = title; - this.#children = children; - } - - id() { - return this.#id; - } - title(language?: string) { - return `${this.#title} - ${language ?? 'default'}`; - } - children() { - return this.#children ?? []; - } -} - -export class GuidelineDocumentClass implements GuidelineDocument { - readonly type = 'document' as const; - readonly #id: number; - readonly #title: string; - readonly #parent: Nullable; - - constructor(id: number, title: string, parent: Nullable = null) { - this.#id = id; - this.#title = title; - this.#parent = parent; - } - - id() { - return this.#id; - } - title(language?: string) { - return `${this.#title} - ${language ?? 'default'}`; - } - slug(language?: string) { - return `${this.#title.toLowerCase().replace(' ', '-')}-${language ?? 'default'}`; - } - url(language?: string) { - return `http://domain.com/document/${this.#id}/${language ?? 'default'}/`; - } - parentId() { - return this.#parent; - } -} - -class GuidelineDocumentLibraryClass implements GuidelineDocumentLibrary { - readonly type = 'document-library' as const; - readonly #id: number; - readonly #title: string; - readonly #parent: Nullable; - - constructor(id: number, title: string, parent: Nullable = null) { - this.#id = id; - this.#title = title; - this.#parent = parent; - } - - id() { - return this.#id; - } - title(language?: string) { - return `${this.#title} - ${language ?? 'default'}`; - } - slug(language?: string) { - return `${this.#title.toLowerCase().replace(' ', '-')}-${language ?? 'default'}`; - } - url(language?: string) { - return `http://domain.com/document/${this.#id}/${language ?? 'default'}/`; - } - parentId() { - return this.#parent; - } -} - -class GuidelineDocumentLinkClass implements GuidelineDocumentLink { - readonly type = 'document-link' as const; - readonly #id: number; - readonly #title: string; - readonly #displayMode: LinkSettingsDisplay = LinkSettingsDisplay.TextOnly; - readonly #icoPosition: LinkSettingsIconPosition = LinkSettingsIconPosition.Left; - readonly #openNewTab: boolean = true; - readonly #parent: Nullable; - - constructor(id: number, title: string, parent: Nullable = null) { - this.#id = id; - this.#title = title; - this.#parent = parent; - } - - id() { - return this.#id; - } - title(language?: string) { - return `${this.#title} - ${language ?? 'default'}`; - } - url() { - return 'http://some-external-domain.com/page'; - } - - displayMode() { - return this.#displayMode; - } - iconPosition() { - return this.#icoPosition; - } - customIconUrl() { - return null; - } - shouldOpenInNewTab() { - return this.#openNewTab; - } - parentId() { - return this.#parent; - } -} - -class GuidelineCoverPageClassDummy { - static with( - id: number, - title = 'Cover Page Name', - published = false, - hiddenFromNavigation = false, - ): GuidelineCoverPageClass { - return new GuidelineCoverPageClass(id, title, published, hiddenFromNavigation); - } -} - -class GuidelineDocumentGroupClassDummy { - static with( - id: number, - title = 'guideline document group', - children: (GuidelineDocumentClass | GuidelineDocumentLibraryClass | GuidelineDocumentLinkClass)[] = [], - ): GuidelineDocumentGroupClass { - return new GuidelineDocumentGroupClass(id, title, children); - } -} - -export class GuidelineDocumentClassDummy { - static with(id: number, title = 'guideline document', parentId: Nullable = null): GuidelineDocumentClass { - return new GuidelineDocumentClass(id, title, parentId); - } -} - -class GuidelineDocumentLibraryClassDummy { - static with( - id: number, - title = 'guideline document library', - parentId: Nullable = null, - ): GuidelineDocumentLibraryClass { - return new GuidelineDocumentLibraryClass(id, title, parentId); - } -} - -class GuidelineDocumentLinkClassDummy { - static with( - id: number, - title = 'guideline document library', - parentId: Nullable = null, - ): GuidelineDocumentLinkClass { - return new GuidelineDocumentLinkClass(id, title, parentId); - } -} - -export class PortalNavigationTreeDummy { - static default(): PortalNavigationItem[] { - return [ - GuidelineCoverPageClassDummy.with(5, 'the Cover Page', true, false), - GuidelineDocumentClassDummy.with(101, 'document-101'), - GuidelineDocumentClassDummy.with(102, 'document-102'), - GuidelineDocumentGroupClassDummy.with(200, 'document-group-200', [ - GuidelineDocumentClassDummy.with(201, 'document-201', 200), - GuidelineDocumentClassDummy.with(202, 'document-203', 200), - GuidelineDocumentClassDummy.with(204, 'document-204', 200), - ]), - GuidelineDocumentLinkClassDummy.with(111, 'document-link-111'), - GuidelineDocumentLibraryClassDummy.with(121, 'document-library-121'), - ]; - } -} diff --git a/packages/app-bridge/src/tests/index.ts b/packages/app-bridge/src/tests/index.ts index d3a1233ac..fdd0078ed 100644 --- a/packages/app-bridge/src/tests/index.ts +++ b/packages/app-bridge/src/tests/index.ts @@ -1,14 +1,10 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ export * from './AppBridgeBlockStub'; -export * from './AppBridgePlatformAppStub'; -export * from './AppBridgeThemeStub'; export * from './AssetApiDummy'; export * from './AssetChooserOptionsDummy'; export * from './AssetChooserResultDummy'; export * from './AssetDummy'; -export * from './BrandportalLinkApiDummy'; -export * from './BrandportalLinkDummy'; export * from './BulkDownloadDummy'; export * from './ColorApiDummy'; export * from './ColorDummy'; @@ -26,8 +22,6 @@ export * from './DocumentPageApiDummy'; export * from './DocumentPageDummy'; export * from './DocumentSectionApiDummy'; export * from './DocumentSectionDummy'; -export * from './GuidelineSearchResultApiDummy'; -export * from './GuidelineSearchResultDummy'; export * from './HttpUtilResponseDummy'; export * from './TargetsApiDummy'; export * from './TargetsDummy'; @@ -37,6 +31,3 @@ export * from './TemplateDummy'; export * from './UserApiDummy'; export * from './UserDummy'; export * from './withAppBridgeBlockStubs'; -export * from './withAppBridgeThemeStubs'; -export * from './PortalNavigationTreeDummy'; -export * from './DocumentNavigationTreeDummy'; diff --git a/packages/app-bridge/src/tests/withAppBridgeThemeStubs.tsx b/packages/app-bridge/src/tests/withAppBridgeThemeStubs.tsx deleted file mode 100644 index 5dc2e2b21..000000000 --- a/packages/app-bridge/src/tests/withAppBridgeThemeStubs.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type ComponentType } from 'react'; - -import { type AppBridgeTheme } from '../AppBridgeTheme'; - -import { getAppBridgeThemeStub, type getAppBridgeThemeStubProps } from './AppBridgeThemeStub'; - -type withAppBridgeThemeStubsProps = { appBridge: AppBridgeTheme }; - -export function withAppBridgeThemeStubs( - WrappedComponent: ComponentType, - appBridgeProps?: getAppBridgeThemeStubProps, -): [ComponentType>, ReturnType] { - const appBridge = getAppBridgeThemeStub(appBridgeProps ?? {}); - const ComponentWithAppBridgeStubs = (props: Omit) => { - return ; - }; - - const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; - ComponentWithAppBridgeStubs.displayName = `withAppBridgeThemeStubs(${displayName})`; - - return [ComponentWithAppBridgeStubs, appBridge]; -} diff --git a/packages/app-bridge/src/types/Asset.ts b/packages/app-bridge/src/types/Asset.ts index 2c4e5bcf8..203a45831 100644 --- a/packages/app-bridge/src/types/Asset.ts +++ b/packages/app-bridge/src/types/Asset.ts @@ -3,6 +3,7 @@ export type AssetApi = { id: number; creator_name: string; + alternative_text: Nullable; ext: string; external_url: Nullable; file_name: string; @@ -29,6 +30,7 @@ export type AssetApi = { export type Asset = { id: number; creatorName: string; + alternativeText: Nullable; extension: string; externalUrl: Nullable; fileName: string; diff --git a/packages/app-bridge/src/types/BrandportalLink.ts b/packages/app-bridge/src/types/BrandportalLink.ts deleted file mode 100644 index 286d4d28e..000000000 --- a/packages/app-bridge/src/types/BrandportalLink.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type BrandportalLinkApi = { - brand_portal: { - enabled: boolean; - label: string; - url: string; - }; -}; - -export type BrandportalLink = BrandportalLinkApi['brand_portal']; diff --git a/packages/app-bridge/src/types/Color.ts b/packages/app-bridge/src/types/Color.ts index 8bb3f210a..f1c983876 100644 --- a/packages/app-bridge/src/types/Color.ts +++ b/packages/app-bridge/src/types/Color.ts @@ -1,5 +1,7 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ +import { type PartialDeep } from 'type-fest'; + export type ColorApi = { id: number; creator: number; @@ -94,36 +96,30 @@ export type ColorApiPatch = Partial<{ export type Color = { id: number; - name: Nullable; sort: number; +} & ColorAsset; + +// This type should be evolved to Asset in the long run +export type ColorAsset = { + title: Nullable; + revision: ColorRevision; +}; + +export type ColorRevision = { nameCss: Nullable; hex: Nullable; - red: Nullable; - green: Nullable; - blue: Nullable; - alpha: Nullable; hue: number; saturation: number; lightness: Nullable; - c: Nullable; - m: Nullable; - y: Nullable; - k: Nullable; - pantone: Nullable; ral: Nullable; oracal: Nullable; - pantoneCoated: Nullable; - pantoneUncoated: Nullable; - cmykCoated: Nullable; - cmykUncoated: Nullable; - cmykNewspaper: Nullable; ncs: Nullable; - pantoneCp: Nullable; - pantonePlastics: Nullable; - pantoneTextile: Nullable; hks: Nullable; threeM: Nullable; lab: Nullable; + cmyk: CmykColor; + rgba: RgbaColor; + pantone: PantoneColor; }; export type ColorCreate = { @@ -134,4 +130,35 @@ export type ColorCreate = { alpha: number; }; -export type ColorPatch = Partial>; +export type ColorPatch = PartialDeep< + Omit & { + title: Nullable; + sort: number; + } +>; + +type RgbaColor = { + red: Nullable; + green: Nullable; + blue: Nullable; + alpha: Nullable; +}; + +type CmykColor = { + cyan: Nullable; + magenta: Nullable; + yellow: Nullable; + black: Nullable; + coated: Nullable; + uncoated: Nullable; + newspaper: Nullable; +}; + +type PantoneColor = { + code: Nullable; + coated: Nullable; + uncoated: Nullable; + cp: Nullable; + plastics: Nullable; + textile: Nullable; +}; diff --git a/packages/app-bridge/src/types/CoverPage.ts b/packages/app-bridge/src/types/CoverPage.ts index 9660b1458..55c0ee2fb 100644 --- a/packages/app-bridge/src/types/CoverPage.ts +++ b/packages/app-bridge/src/types/CoverPage.ts @@ -31,12 +31,3 @@ export type CoverPage = Merge< enabled: boolean; } >; - -export type CoverPageCreate = PickRequired, 'id'>, 'documentId' | 'template'>; -export type CoverPageUpdate = RequireOnlyOne; - -export type CoverPageUpdateLegacy = { - brandhome_draft?: boolean; - brandhome_title?: string; - brandhome_hide_in_nav?: boolean; -}; diff --git a/packages/app-bridge/src/types/CrossDocumentMessage.ts b/packages/app-bridge/src/types/CrossDocumentMessage.ts deleted file mode 100644 index 431189471..000000000 --- a/packages/app-bridge/src/types/CrossDocumentMessage.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type Topic } from './Topic'; - -export type CrossDocumentMessage> = { - topic: Topic; - token: string; - data?: T; -}; diff --git a/packages/app-bridge/src/types/CrossDocumentMessageResponse.ts b/packages/app-bridge/src/types/CrossDocumentMessageResponse.ts deleted file mode 100644 index 0b171d6b0..000000000 --- a/packages/app-bridge/src/types/CrossDocumentMessageResponse.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type Topic } from './Topic'; - -export type CrossDocumentMessageResponse = { - success: boolean; - topic: Topic; - token: string; - data?: T; -}; diff --git a/packages/app-bridge/src/types/DispatchHandler.ts b/packages/app-bridge/src/types/DispatchHandler.ts index 87f968352..90328eee0 100644 --- a/packages/app-bridge/src/types/DispatchHandler.ts +++ b/packages/app-bridge/src/types/DispatchHandler.ts @@ -8,7 +8,6 @@ export type DispatchPayload = { openAssetViewer: { token: string }; openTemplateChooser: void; closeTemplateChooser: void; - openNavigationManager: void; openNewPublication: OpenNewPublicationPayload; }; diff --git a/packages/app-bridge/src/types/Document.ts b/packages/app-bridge/src/types/Document.ts index 1dc311e51..2ab798f22 100644 --- a/packages/app-bridge/src/types/Document.ts +++ b/packages/app-bridge/src/types/Document.ts @@ -30,7 +30,7 @@ type DocumentApiAsNoneLink = { link_url: Nullable; }; -export type DocumentLinkSettingsApi = { +type DocumentLinkSettingsApi = { new_tab: boolean; display?: LinkSettingsDisplay; icon_position?: LinkSettingsIconPosition; diff --git a/packages/app-bridge/src/types/DocumentSection.ts b/packages/app-bridge/src/types/DocumentSection.ts index 84b54b24e..3304368ab 100644 --- a/packages/app-bridge/src/types/DocumentSection.ts +++ b/packages/app-bridge/src/types/DocumentSection.ts @@ -6,7 +6,7 @@ export type DocumentSectionApi = { page_id: number; slug: string; sort: number; - title: string; + title: Nullable; creator: number; created: string; modifier: Nullable; @@ -17,7 +17,7 @@ export type DocumentSectionApi = { export type DocumentSection = { id: number; - title: string; + title: Nullable; slug: string; sort: number; permanentLink: string; @@ -31,7 +31,7 @@ export type AddDocumentSectionPayload = { export type UpdateDocumentSectionPayload = { id: number; - title: string; + title: Nullable; slug: string; documentPageId: number; }; diff --git a/packages/app-bridge/src/types/Emitter.ts b/packages/app-bridge/src/types/Emitter.ts index 4e7c000c2..a5d0db567 100644 --- a/packages/app-bridge/src/types/Emitter.ts +++ b/packages/app-bridge/src/types/Emitter.ts @@ -3,10 +3,8 @@ import { type BlockSettingsUpdateEvent } from '../react/useBlockSettings'; import { type Asset } from './Asset'; -import { type BrandportalLink } from './BrandportalLink'; import { type Color } from './Color'; import { type ColorPalette } from './ColorPalette'; -import { type CoverPage } from './CoverPage'; import { type Document } from './Document'; import { type DocumentCategory } from './DocumentCategory'; import { type DocumentGroup } from './DocumentGroup'; @@ -19,13 +17,10 @@ import { import { type PrivacySettings } from './PrivacySettings'; import { type Template } from './Template'; import { type AssetViewerOptions } from './Terrific'; -import { type ThemeTemplate } from './ThemeTemplate'; export type EmitterAction = 'add' | 'update' | 'delete'; export type EmitterEvents = { - 'AppBridge:PageTemplateSettingsUpdated': { pageTemplateSettings: Record }; - 'AppBridge:ThemeSettingsUpdated': { themeSettings: Record> }; 'AppBridge:BlockSettingsUpdated': BlockSettingsUpdateEvent; 'AppBridge:BlockAssetsUpdated': { @@ -34,21 +29,6 @@ export type EmitterEvents = { prevBlockAssets: Record; }; - 'AppBridge:TemplateAssetsUpdated': { - template: ThemeTemplate; - documentId?: number; - documentPageId?: number; - templateAssets: Record; - prevTemplateAssets: Record; - }; - - 'AppBridge:ThemeAssetsUpdated': { - portalId: number; - themeAssets: Record; - prevThemeAssets: Record; - template: ThemeTemplate; - }; - 'AppBridge:BlockTemplatesUpdated': { blockId: number; blockTemplates: Record; @@ -87,27 +67,10 @@ export type EmitterEvents = { action: 'delete'; }; - 'AppBridge:GuidelineCoverPage:Action': - | { - coverPage: CoverPage; - action: 'add' | 'update'; - } - | { - action: 'delete'; - }; - - 'AppBridge:GuidelineBrandportalLink:Action': { - brandportalLink: BrandportalLink; - action: 'update'; - }; - 'AppBridge:PrivacySettingsChanged': PrivacySettings; 'AppBridge:OpenNavigationManager': void; - 'AppBridge:OpenSearchDialog': void; - 'AppBridge:CloseSearchDialog': void; - 'AppBridge:GuidelineDocumentPage:Action': | { documentPage: DocumentPage; @@ -142,7 +105,7 @@ export type EmitterEvents = { }; 'AppBridge:GuidelineDocumentCategory:DocumentPageAction': { - documentPage: { id: number; categoryId: number }; + documentPage: { id: number; categoryId: number; documentId: number }; action: 'add' | 'delete'; }; diff --git a/packages/app-bridge/src/types/Guideline.ts b/packages/app-bridge/src/types/Guideline.ts deleted file mode 100644 index 26c09120f..000000000 --- a/packages/app-bridge/src/types/Guideline.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type LinkSettingsDisplay, type LinkSettingsIconPosition } from './Document.ts'; - -export interface GuidelineCoverPage { - id(): number; - title(language?: string): string; - isPublished(): boolean; - isHiddenInNavigation(): boolean; - url(language?: string): string; - type: 'cover-page'; -} - -export interface GuidelineDocumentGroup { - id(): number; - title(language?: string): string; - children(): (GuidelineDocument | GuidelineDocumentLibrary | GuidelineDocumentLink)[]; - type: 'document-group'; -} - -export interface GuidelineDocument { - id(): number; - title(language?: string): string; - slug(language?: string): string; - url(language?: string): string; - parentId(): Nullable; - type: 'document'; -} - -export interface GuidelineDocumentLibrary { - id(): number; - title(language?: string): string; - slug(language?: string): string; - url(language?: string): string; - parentId(): Nullable; - type: 'document-library'; -} - -export interface GuidelineDocumentLink { - id(): number; - title(language?: string): string; - url(): string; - displayMode(): LinkSettingsDisplay; - iconPosition(): LinkSettingsIconPosition; - customIconUrl(): Nullable; - shouldOpenInNewTab(): boolean; - parentId(): Nullable; - type: 'document-link'; -} - -export interface GuidelinePageCategory { - id(): number; - title(language?: string): string; - slug(language?: string): string; - children(): (GuidelineDocumentPage | GuidelineDocumentPageLink)[]; - type: 'page-category'; -} - -export interface GuidelineDocumentPage { - id(): number; - title(language?: string): string; - slug(language?: string): string; - url(language?: string): string; - headings(): GuidelineDocumentPageHeading[]; - type: 'document-page'; -} - -export interface GuidelineDocumentPageLink { - id(): number; - title(language?: string): string; - url(): string; - type: 'document-page-link'; -} - -export interface GuidelineDocumentPageHeading { - id(): number; - title(language?: string): string; - slug(language?: string): string; - type: 'document-page-heading'; -} - -export type PortalNavigationItem = - | GuidelineCoverPage - | GuidelineDocumentGroup - | GuidelineDocument - | GuidelineDocumentLibrary - | GuidelineDocumentLink; - -export type DocumentNavigationItem = - | GuidelinePageCategory - | GuidelineDocumentPage - | GuidelineDocumentPageLink - | GuidelineDocumentPageHeading; diff --git a/packages/app-bridge/src/types/GuidelineSearchResult.ts b/packages/app-bridge/src/types/GuidelineSearchResult.ts deleted file mode 100644 index 3b39d72e9..000000000 --- a/packages/app-bridge/src/types/GuidelineSearchResult.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type CamelCasedPropertiesDeep } from 'type-fest'; - -export const GuidelineSearchResultTypeMap = { - block: 'BLOCK', - section: 'SECTION', - page: 'PAGE', - color: 'COLOR', -} as const; -type GuidelineSearchResultType = (typeof GuidelineSearchResultTypeMap)[keyof typeof GuidelineSearchResultTypeMap]; - -export type GuidelineSearchResultApi = { - highlights: string[]; - type: GuidelineSearchResultType; - object_id: number; - page_id: number; - page_slug: string; - page_title: string; - page_category_slug: string | null; - block_id: number; - document_id: number; - document_slug: string; - document_title: string; - guideline_title: string; - portal_id: number; - portal_token: string | null; - section_id: string | null; - section_slug: string | null; - section_title: string | null; - color_hex?: string; - project_color_id?: string; -}; - -export type GuidelineSearchResult = CamelCasedPropertiesDeep; diff --git a/packages/app-bridge/src/types/Notify.ts b/packages/app-bridge/src/types/Notify.ts deleted file mode 100644 index 0b619a4f0..000000000 --- a/packages/app-bridge/src/types/Notify.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type NotifyOptions = { - origin?: string; -}; - -export type NotifyData> = T; diff --git a/packages/app-bridge/src/types/OauthTokens.ts b/packages/app-bridge/src/types/OauthTokens.ts deleted file mode 100644 index 93c3baa1f..000000000 --- a/packages/app-bridge/src/types/OauthTokens.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type OauthTokens = { - access_token: string; - refresh_token: string; - expires_in: number; - name: string; - token_type: 'Bearer'; -}; diff --git a/packages/app-bridge/src/types/PostExternalAssetParams.ts b/packages/app-bridge/src/types/PostExternalAssetParams.ts deleted file mode 100644 index 13af5588d..000000000 --- a/packages/app-bridge/src/types/PostExternalAssetParams.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export type PostExternalAssetParams = { - title: string; - url: string; - previewUrl?: string; - data?: Record; -}; diff --git a/packages/app-bridge/src/types/Project.ts b/packages/app-bridge/src/types/Project.ts deleted file mode 100644 index 6a65d7aff..000000000 --- a/packages/app-bridge/src/types/Project.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type CamelCasedPropertiesDeep } from 'type-fest'; - -import { type DocumentLibraryMode } from './Document'; - -export type ProjectApi = { - id: number; - created: string; - creator: number; - name: string; - slug: string; - account: number; - token: string; - email_notification: number; - user_count: number; - brand_id: number; - project_type: string; - auto_tagging_enabled: Nullable; - date_begin: Nullable; - date_end: Nullable; - is_template: number; - download_sizes: string; - zoom_level: string; - success: boolean; - project: { - id: number; - url: string; - slug: string; - creator: number; - creator_name: string; - creator_initials: string; - name: string; - description: Nullable; - account: number; - }; -}; - -export type ProjectCreateApi = { - name: string; - brand: number; - styleguide: number; - project_type: DocumentLibraryMode; -}; - -export type Project = CamelCasedPropertiesDeep; - -export type ProjectCreate = CamelCasedPropertiesDeep; diff --git a/packages/app-bridge/src/types/Terrific.ts b/packages/app-bridge/src/types/Terrific.ts index b7528203c..b1683ea4d 100644 --- a/packages/app-bridge/src/types/Terrific.ts +++ b/packages/app-bridge/src/types/Terrific.ts @@ -17,7 +17,11 @@ export type TerrificComponent = { }; }; -export type AssetChooserResult = Omit & { project: number; filesize: number }; +export type AssetChooserResult = Omit & { + project: number; + filesize: number; + computed_alternative_text: Nullable; +}; export type TemplateChooserResult = TemplateApiLegacy; export type AssetChooserAssetChosenCallback = (selectedAsset: { screenData: AssetChooserResult[] }) => void; diff --git a/packages/app-bridge/src/types/Topic.ts b/packages/app-bridge/src/types/Topic.ts index 4f0bf54f6..7d1093b10 100644 --- a/packages/app-bridge/src/types/Topic.ts +++ b/packages/app-bridge/src/types/Topic.ts @@ -11,5 +11,4 @@ export enum Topic { OpenAssetChooser = 'openAssetChooser', PostExternalAssets = 'postExternalAssets', PutAppState = 'putAppState', - Init = 'Init', } diff --git a/packages/app-bridge/src/types/index.ts b/packages/app-bridge/src/types/index.ts index 6085cd036..2a059c20e 100644 --- a/packages/app-bridge/src/types/index.ts +++ b/packages/app-bridge/src/types/index.ts @@ -1,13 +1,10 @@ /* (c) Copyright Frontify Ltd., all rights reserved. */ export * from './Asset'; -export * from './BrandportalLink'; export * from './BulkDownload'; export * from './Color'; export * from './ColorPalette'; export * from './CoverPage'; -export * from './CrossDocumentMessage'; -export * from './CrossDocumentMessageResponse'; export * from './DispatchHandler'; export * from './Document'; export * from './DocumentBlockTemplate'; @@ -20,20 +17,12 @@ export * from './File'; export * from './FileExtension'; export * from './FileExtensionSets'; export * from './FileType'; -export * from './Guideline'; -export * from './GuidelineSearchResult'; -export * from './Language'; -export * from './Notify'; -export * from './OauthTokens'; export * from './PageTemplateAsset'; export * from './PrivacySettings'; -export * from './PostExternalAssetParams'; -export * from './Project'; export * from './SubscriptionHandler'; export * from './Targets'; export * from './TemplateLegacy'; export * from './Template'; export * from './Terrific'; -export * from './ThemeTemplate'; export * from './Topic'; export * from './User'; diff --git a/packages/app-bridge/src/utilities/MessageBus.spec.ts b/packages/app-bridge/src/utilities/MessageBus.spec.ts deleted file mode 100644 index 7e1831429..000000000 --- a/packages/app-bridge/src/utilities/MessageBus.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it, vi } from 'vitest'; - -import { MessageBus } from './MessageBus'; - -describe('MessageBus', () => { - it('should be instantiable', () => { - const { port1 } = new MessageChannel(); - - const messageBus = new MessageBus(port1); - expect(messageBus).toBeInstanceOf(MessageBus); - }); - - it('should call postMessage from the MessageChannel on post', () => { - const postMessageMock = vi.fn(); - - class PortMock { - postMessage = postMessageMock; - onmessage = vi.fn(); - onmessageerror = vi.fn(); - } - - const messageBus = new MessageBus(new PortMock() as unknown as MessagePort); - messageBus.post({ operation: 'test' }); - expect(postMessageMock).toHaveBeenCalledTimes(1); - }); - - it('should return message from port2', async () => { - const channel = new MessageChannel(); - - const messageBus = new MessageBus(channel.port1); - - const testResponse = 'test-message'; - channel.port2.onmessage = (event) => { - const { token } = event.data; - channel.port2.postMessage({ message: testResponse, token }); - }; - const result = await messageBus.post({ operation: 'test' }); - - expect(result).toEqual(testResponse); - }); - - it('should trigger onmessage error on message sending error', async () => { - const channel = new MessageChannel(); - const messageBus = new MessageBus(channel.port1); - - channel.port1.postMessage = () => { - throw new Error('Simulated message sending error'); - }; - - await expect(() => messageBus.post({ operation: 'test' })).rejects.toThrow(); - }); - - it('should send a token with the channel', async () => { - const channel = new MessageChannel(); - const messageBus = new MessageBus(channel.port1); - - channel.port2.onmessage = (event) => { - expect(event.data.token).toBeDefined(); - }; - - messageBus.post({ operation: 'test' }); - }); - - it('should return a message with a token', async () => { - const channel = new MessageChannel(); - const messageBus = new MessageBus(channel.port1); - - channel.port2.onmessage = (event) => { - const { token } = event.data; - channel.port2.postMessage({ message: 'test-message', token }); - }; - - const response = await messageBus.post({ operation: 'test' }); - expect(response).toBe('test-message'); - }); - - it('should handle multiple messages with their tokens and return correct message', async () => { - const channel = new MessageChannel(); - const messageBus = new MessageBus(channel.port1); - - const message1 = 'test-message-1'; - const message2 = 'test-message-2'; - const message3 = 'test-message-3'; - - channel.port2.onmessage = (event) => { - const { token, message } = event.data; - if (message.operation === 'message1') { - setTimeout(() => { - channel.port2.postMessage({ message: message1, token }); - }, 1); - } else if (message.operation === 'message2') { - setTimeout(() => { - channel.port2.postMessage({ message: message2, token }); - }, 2); - } else if (message.operation === 'message3') { - channel.port2.postMessage({ message: message3, token }); - } - }; - - const response2 = await messageBus.post({ operation: 'message2' }); - const response1 = await messageBus.post({ operation: 'message1' }); - const response3 = await messageBus.post({ operation: 'message3' }); - - const allPromises = await Promise.all([ - messageBus.post({ operation: 'message3' }), - messageBus.post({ operation: 'message1' }), - messageBus.post({ operation: 'message2' }), - ]); - - expect(response1).toBe(message1); - expect(response2).toBe(message2); - expect(response3).toBe(message3); - - expect(allPromises.includes(message1)).toBe(true); - expect(allPromises.includes(message2)).toBe(true); - expect(allPromises.includes(message3)).toBe(true); - }); -}); diff --git a/packages/app-bridge/src/utilities/MessageBus.ts b/packages/app-bridge/src/utilities/MessageBus.ts deleted file mode 100644 index cf7f35265..000000000 --- a/packages/app-bridge/src/utilities/MessageBus.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { InitializationError, TimeoutReachedError } from '../errors'; - -import { generateRandomString } from './hash'; -import { SUBSCRIBE_TIMEOUT } from './subscribe'; - -export interface IMessageBus { - post(message: { parameter: unknown }): unknown; -} - -export class MessageBus implements IMessageBus { - private messageBucket: { - message: unknown; - token: string; - resolve: (value: unknown) => void; - }[] = []; - - constructor(private port: MessagePort) { - this.port.onmessage = (event) => { - const { token } = event.data; - const messageIndex = this.messageBucket.findIndex((item) => item.token === token); - if (messageIndex > -1) { - const message = this.messageBucket.splice(messageIndex, 1)[0]; - message.resolve(event.data.message); - } - }; - } - - public post(message: unknown) { - return new Promise((resolve, reject) => { - const token = generateRandomString(); - - this.messageBucket.push({ message, resolve, token }); - this.port.postMessage({ message, token }); - - setTimeout(() => { - reject(new TimeoutReachedError('operation')); - }, SUBSCRIBE_TIMEOUT * 10); - }); - } -} - -export class ErrorMessageBus implements IMessageBus { - post() { - throw new InitializationError('First use await platformApp.dispatch({ name: "openConnection" })'); - } -} diff --git a/packages/app-bridge/src/utilities/file.ts b/packages/app-bridge/src/utilities/file.ts index a296c8fed..c318db5b1 100644 --- a/packages/app-bridge/src/utilities/file.ts +++ b/packages/app-bridge/src/utilities/file.ts @@ -2,6 +2,11 @@ import { FileExtension } from '../types/FileExtension'; +export const getMimeType = (filetypes: string[]): string[] => { + return filetypes + .map((filetype) => mimeTypes[filetype as keyof typeof mimeTypes]) + .filter((mimeType) => mimeType !== undefined); +}; export const mimeTypes: Record = { [FileExtension.Aac]: 'audio/aac', [FileExtension.Ac3]: 'audio/ac3', @@ -211,9 +216,3 @@ export const mimeTypes: Record = { [FileExtension.Xyz]: 'chemical/x-xyz', [FileExtension.Zip]: 'application/zip', }; - -export const getMimeType = (filetypes: string[]): string[] => { - return filetypes - .map((filetype) => mimeTypes[filetype as keyof typeof mimeTypes]) - .filter((mimeType) => mimeType !== undefined); -}; diff --git a/packages/app-bridge/src/utilities/hash.ts b/packages/app-bridge/src/utilities/hash.ts deleted file mode 100644 index bcbceb84a..000000000 --- a/packages/app-bridge/src/utilities/hash.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -export const generateRandomString = (length = 6): string => { - return Math.random().toString(20).substr(2, length); -}; diff --git a/packages/app-bridge/src/utilities/index.ts b/packages/app-bridge/src/utilities/index.ts index d63b7c1ae..50afb9aee 100644 --- a/packages/app-bridge/src/utilities/index.ts +++ b/packages/app-bridge/src/utilities/index.ts @@ -3,8 +3,5 @@ export * from './color'; export * from './dom'; export * from './file'; -export * from './hash'; export * from './httpClient'; -export * from './notify'; export * from './object'; -export * from './subscribe'; diff --git a/packages/app-bridge/src/utilities/notify.ts b/packages/app-bridge/src/utilities/notify.ts deleted file mode 100644 index 7a08cdb4f..000000000 --- a/packages/app-bridge/src/utilities/notify.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { type NotifyData, type NotifyOptions } from '../types'; - -export function notify(topic: string, token: string, data?: NotifyData, options?: NotifyOptions): void { - const parentWindow = window.top; - parentWindow?.postMessage( - { - topic, - token, - data, - }, - options?.origin || '*', - ); -} diff --git a/packages/app-bridge/src/utilities/queryParams.spec.ts b/packages/app-bridge/src/utilities/queryParams.spec.ts deleted file mode 100644 index 2052bc938..000000000 --- a/packages/app-bridge/src/utilities/queryParams.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { describe, expect, it } from 'vitest'; - -import { getQueryParameters } from './queryParams'; - -describe('queryParams', () => { - it('should return correct queryParams from url', () => { - expect(getQueryParameters('https://www.frontify.com?foo=bar&baz=qux')).toEqual({ foo: 'bar', baz: 'qux' }); - }); - - it('should return empty object if no queryParams are present', () => { - expect(getQueryParameters('https://www.frontify.com')).toEqual({}); - }); - - it('should return empty object if url is empty', () => { - expect(() => getQueryParameters('')).toThrowError('Validation failed. "" is not a valid url'); - }); - - it('should return empty object if url is undefined', () => { - expect(() => getQueryParameters(undefined as unknown as string)).toThrowError( - 'Validation failed. "undefined" is not a valid url', - ); - }); - - it('should return empty object if url is null', () => { - expect(() => getQueryParameters(null as unknown as string)).toThrowError( - 'Validation failed. "null" is not a valid url', - ); - }); - - it('should return objects of well formed query parameters', () => { - expect(getQueryParameters('https://www.frontify.com?foo=bar&baz')).toEqual({ foo: 'bar', baz: '' }); - }); -}); diff --git a/packages/app-bridge/src/utilities/queryParams.ts b/packages/app-bridge/src/utilities/queryParams.ts deleted file mode 100644 index dbf3936b4..000000000 --- a/packages/app-bridge/src/utilities/queryParams.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { ValidationError } from '../errors/ValidationError'; - -export type QueryParams = { [key: string]: string }; - -export const getQueryParameters = (url: string): QueryParams => { - const paramsToObject = (entries: IterableIterator<[string, string]>) => { - const result: QueryParams = {}; - - for (const [key, value] of entries) { - result[key] = value; - } - - return result; - }; - - try { - const urlObject = new URL(url); - return paramsToObject(urlObject.searchParams.entries()); - } catch { - throw new ValidationError(`"${url}" is not a valid url`); - } -}; diff --git a/packages/app-bridge/src/utilities/subscribe.ts b/packages/app-bridge/src/utilities/subscribe.ts deleted file mode 100644 index 902e37812..000000000 --- a/packages/app-bridge/src/utilities/subscribe.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* (c) Copyright Frontify Ltd., all rights reserved. */ - -import { FetchError } from '../errors/NotifyError'; -import { TimeoutReachedError } from '../errors/TimeoutReachedError'; -import { type CrossDocumentMessageResponse, type Topic } from '../types'; - -export type SubscribeOptions = { - timeout?: number; -}; - -export const SUBSCRIBE_TIMEOUT = 3 * 1000; - -export function subscribe(topic: Topic, token: string, options?: SubscribeOptions): Promise { - return new Promise((resolve, reject) => { - const subscribeResponseCallback = (event: MessageEvent) => { - const response: CrossDocumentMessageResponse = event.data; - - if (response.token === token && response.topic === topic && response.success) { - resolve((response.data || true)); - } else { - reject(new FetchError(topic)); - } - - window.removeEventListener('message', subscribeResponseCallback); - }; - - window.addEventListener('message', subscribeResponseCallback); - - setTimeout(() => { - reject(new TimeoutReachedError(topic)); - }, options?.timeout || SUBSCRIBE_TIMEOUT); - }); -} diff --git a/packages/guideline-themes/.eslintrc.cjs b/packages/guideline-themes/.eslintrc.cjs new file mode 100644 index 000000000..04ef1024d --- /dev/null +++ b/packages/guideline-themes/.eslintrc.cjs @@ -0,0 +1,58 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +module.exports = { + root: true, + extends: ['@frontify/eslint-config-react'], + plugins: ['notice'], + settings: { + react: { + version: 'detect', + }, + }, + parserOptions: { + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + overrides: [ + { + files: ['*.js', '*.ts', '*.tsx'], + rules: { + 'notice/notice': [ + 'error', + { + template: '/* (c) Copyright Frontify Ltd., all rights reserved. */\n\n', + messages: { + whenFailedToMatch: 'No Frontify copyright header set.', + }, + }, + ], + }, + }, + { + files: ['*.ts', '*.tsx', '*.mts', '*.cts'], + rules: { + 'no-prototype-builtins': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-misused-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/await-thenable': 'warn', + '@typescript-eslint/no-unsafe-enum-comparison': 'warn', + '@typescript-eslint/restrict-plus-operands': 'warn', + }, + }, + { + files: ['**/*.md/**/*'], + processor: 'markdown/markdown', + parserOptions: { + project: null, + }, + }, + ], +}; diff --git a/packages/guideline-themes/.gitignore b/packages/guideline-themes/.gitignore new file mode 100644 index 000000000..de4d1f007 --- /dev/null +++ b/packages/guideline-themes/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/packages/guideline-themes/.prettierrc b/packages/guideline-themes/.prettierrc new file mode 100644 index 000000000..9dd9fc632 --- /dev/null +++ b/packages/guideline-themes/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "tabWidth": 4, + "printWidth": 120, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/packages/guideline-themes/CHANGELOG.md b/packages/guideline-themes/CHANGELOG.md new file mode 100644 index 000000000..341eab730 --- /dev/null +++ b/packages/guideline-themes/CHANGELOG.md @@ -0,0 +1,101 @@ +# @frontify/guideline-themes + +## 0.0.1-alpha.14 + +### Patch Changes + +- Updated dependencies [[`10712c0`](https://github.com/Frontify/brand-sdk/commit/10712c01e2b5b4979b8649525515e490b6c4125b)]: + - @frontify/app-bridge-theme@0.0.0-alpha.27 + +## 0.0.1-alpha.13 + +### Patch Changes + +- Updated dependencies [[`22226ab`](https://github.com/Frontify/brand-sdk/commit/22226ab1266c83a8206a36431a419ad215ecbcb6)]: + - @frontify/app-bridge-theme@0.0.0-alpha.26 + +## 0.0.1-alpha.12 + +### Patch Changes + +- Updated dependencies [[`e8f80db`](https://github.com/Frontify/brand-sdk/commit/e8f80db9ebff09c575acc932f22ce65886c4d7ee), [`242db22`](https://github.com/Frontify/brand-sdk/commit/242db22097bbb8f14187ef5af42d2cd6374a8357)]: + - @frontify/app-bridge-theme@0.0.0-alpha.25 + +## 0.0.1-alpha.11 + +### Patch Changes + +- [#1212](https://github.com/Frontify/brand-sdk/pull/1212) [`b1f7ebf`](https://github.com/Frontify/brand-sdk/commit/b1f7ebf02cf5f77b1fe2d1286a4d9727ec49d25a) Thanks [@Kenny806](https://github.com/Kenny806)! - chore: update the @frontify/sidebar-settings dependency + +## 0.0.1-alpha.10 + +### Patch Changes + +- Updated dependencies [[`0324ffb`](https://github.com/Frontify/brand-sdk/commit/0324ffb2b108d33a83403cfd796c3445626f230a)]: + - @frontify/app-bridge-theme@0.0.0-alpha.24 + +## 0.0.1-alpha.9 + +### Patch Changes + +- Updated dependencies [[`b8d3922`](https://github.com/Frontify/brand-sdk/commit/b8d3922f5e10445426e9c9930ab004e41d8c3fb7)]: + - @frontify/app-bridge-theme@0.0.0-alpha.23 + +## 0.0.1-alpha.8 + +### Patch Changes + +- Updated dependencies [[`265c4d4`](https://github.com/Frontify/brand-sdk/commit/265c4d48a76cf5bd6e257123f0d793bd930f91da)]: + - @frontify/app-bridge-theme@0.0.0-alpha.22 + +## 0.0.1-alpha.7 + +### Patch Changes + +- Updated dependencies [[`d3f4ac4`](https://github.com/Frontify/brand-sdk/commit/d3f4ac4385770dcfe6672dfd2f256f39d1473563)]: + - @frontify/app-bridge-theme@0.0.0-alpha.21 + +## 0.0.1-alpha.6 + +### Patch Changes + +- [#1145](https://github.com/Frontify/brand-sdk/pull/1145) [`ddf65a6`](https://github.com/Frontify/brand-sdk/commit/ddf65a69e43d127213486f6f1a45394bc05eb0cf) Thanks [@anxobotana](https://github.com/anxobotana)! - refactor(guideline-themes): removes themeConfigExport.settings + +## 0.0.1-alpha.5 + +### Patch Changes + +- Updated dependencies [[`18560db`](https://github.com/Frontify/brand-sdk/commit/18560dbd1bbfa3a76fdda0db5ed520b670c41979)]: + - @frontify/app-bridge-theme@0.0.0-alpha.20 + +## 0.0.1-alpha.4 + +### Patch Changes + +- [#1105](https://github.com/Frontify/brand-sdk/pull/1105) [`35e5f5d`](https://github.com/Frontify/brand-sdk/commit/35e5f5d928de04b4884629a2586e448587ac2c9d) Thanks [@anxobotana](https://github.com/anxobotana)! - fix(guideline-themes): export types ungroupped + +## 0.0.1-alpha.3 + +### Patch Changes + +- [#1103](https://github.com/Frontify/brand-sdk/pull/1103) [`6a57f04`](https://github.com/Frontify/brand-sdk/commit/6a57f04c75a05ef177fe4185f9e768ad72dafd89) Thanks [@mike85](https://github.com/mike85)! - refactor(GuidelineThemes): type cleanup + +## 0.0.1-alpha.2 + +### Patch Changes + +- Updated dependencies [[`024b908`](https://github.com/Frontify/brand-sdk/commit/024b9089f68482aa908f08936c6a0c33cdaafb6c)]: + - @frontify/app-bridge-theme@0.0.0-alpha.19 + +## 0.0.1-alpha.1 + +### Patch Changes + +- Updated dependencies [[`a70a9fe`](https://github.com/Frontify/brand-sdk/commit/a70a9fe0932e1a40c5d4d85e4fdcb3f008947b74)]: + - @frontify/app-bridge-theme@0.0.0-alpha.18 + +## 0.0.1-alpha.0 + +### Patch Changes + +- [#1087](https://github.com/Frontify/brand-sdk/pull/1087) [`fc3ee83`](https://github.com/Frontify/brand-sdk/commit/fc3ee83b7a4dd406c386431d0b72cf9873e9dfed) Thanks [@anxobotana](https://github.com/anxobotana)! - feat: starting guideline-themes package diff --git a/packages/guideline-themes/README.md b/packages/guideline-themes/README.md new file mode 100644 index 000000000..bd8b3ccbd --- /dev/null +++ b/packages/guideline-themes/README.md @@ -0,0 +1,3 @@ +# Themes types + +Provides typing for guideline themes diff --git a/packages/guideline-themes/package.json b/packages/guideline-themes/package.json new file mode 100644 index 000000000..fc5759642 --- /dev/null +++ b/packages/guideline-themes/package.json @@ -0,0 +1,60 @@ +{ + "name": "@frontify/guideline-themes", + "type": "module", + "version": "0.0.1-alpha.14", + "description": "Provides types and helpers for the guideline themes development", + "author": "Frontify Developers ", + "repository": { + "type": "git", + "url": "https://github.com/Frontify/brand-sdk", + "directory": "packages/guideline-themes" + }, + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.umd.js", + "import": "./dist/index.es.js" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "dev": "vite build --watch", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "prettier": "prettier --check .", + "prettier:fix": "prettier --write .", + "test": "vitest run --silent", + "test:coverage": "vitest --coverage", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@frontify/app-bridge-theme": "workspace:^", + "@frontify/sidebar-settings": "^0.10.1" + }, + "devDependencies": { + "@frontify/eslint-config-react": "0.17.6", + "@types/node": "^18.19.31", + "@types/react": "^18.2.75", + "@vitest/coverage-v8": "^1.4.0", + "@vitest/ui": "^1.4.0", + "eslint": "^8.57.0", + "eslint-plugin-notice": "^0.9.10", + "prettier": "^3.2.5", + "typescript": "^5.4.4", + "vite": "^5.2.10", + "vite-plugin-dts": "^3.8.1", + "vite-plugin-externals": "^0.6.2", + "vitest": "^1.4.0" + } +} diff --git a/packages/guideline-themes/setupTests.ts b/packages/guideline-themes/setupTests.ts new file mode 100644 index 000000000..78bfeb7d6 --- /dev/null +++ b/packages/guideline-themes/setupTests.ts @@ -0,0 +1,13 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { afterEach, vi } from 'vitest'; + +vi.stubGlobal('crypto', { + getRandomValues: vi.fn(), +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +globalThis.structuredClone = (data: unknown) => JSON.parse(JSON.stringify(data)); diff --git a/packages/guideline-themes/src/index.ts b/packages/guideline-themes/src/index.ts new file mode 100644 index 000000000..84eed249f --- /dev/null +++ b/packages/guideline-themes/src/index.ts @@ -0,0 +1,104 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { type AppBridgeTheme, type ThemeTemplate } from '@frontify/app-bridge-theme'; +import { + type AssetInputBlock as AssetInputBlockSidebarSettings, + type BaseBlock as BaseBlockSidebarSettings, + type Bundle as BundleSidebarSettings, + type ChecklistBlock as ChecklistBlockSidebarSettings, + type ChoicesType as ChoicesTypeSidebarSettings, + type ColorInputBlock as ColorInputBlockSidebarSettings, + type DropdownBlock as DropdownBlockSidebarSettings, + type DynamicSettingBlock as DynamicSettingBlockSidebarSettings, + type DynamicSupportedBlock as DynamicSupportedBlockSidebarSettings, + type InputBlock as InputBlockSidebarSettings, + type LegacyAssetInputBlock as LegacyAssetInputBlockSidebarSettings, + type LinkChooserBlock as LinkChooserBlockSidebarSettings, + type MultiInputBlock as MultiInputBlockSidebarSettings, + type NotificationBlock as NotificationBlockSidebarSettings, + type SectionHeadingBlock as SectionHeadingBlockSidebarSettings, + type SegmentedControlsBlock as SegmentedControlsBlockSidebarSettings, + type SettingBlock as SettingBlockSidebarSettings, + type SimpleSettingBlock as SimpleSettingBlockSidebarSettings, + type SwitchBlock as SwitchBlockSidebarSettings, + type TemplateInputBlock as TemplateInputBlockSidebarSettings, + type TextareaBlock as TextareaBlockSidebarSettings, + type ValueOrPromisedValue as ValueOrPromisedValueSidebarSettings, +} from '@frontify/sidebar-settings'; +import { type FC } from 'react'; + +export * from '@frontify/sidebar-settings'; + +export * from './utilities'; + +export type AssetInputBlock = AssetInputBlockSidebarSettings; +export type BaseBlock = BaseBlockSidebarSettings; +export type Bundle = BundleSidebarSettings; +export type ChecklistBlock = ChecklistBlockSidebarSettings; +export type ChoicesType = ChoicesTypeSidebarSettings; +export type ColorInputBlock = ColorInputBlockSidebarSettings; +export type DropdownBlock = DropdownBlockSidebarSettings; +export type DynamicSettingBlock = + DynamicSettingBlockSidebarSettings; +export type DynamicSupportedBlock = DynamicSupportedBlockSidebarSettings; +export type InputBlock = InputBlockSidebarSettings; +export type LegacyAssetInputBlock = LegacyAssetInputBlockSidebarSettings; +export type LinkChooserBlock = LinkChooserBlockSidebarSettings; +export type MultiInputBlock = MultiInputBlockSidebarSettings; +export type NotificationBlock = NotificationBlockSidebarSettings; +export type SectionHeadingBlock = SectionHeadingBlockSidebarSettings; +export type SegmentedControlsBlock = SegmentedControlsBlockSidebarSettings; +export type SettingBlock = SettingBlockSidebarSettings; +export type SimpleSettingBlock = SimpleSettingBlockSidebarSettings; +export type SwitchBlock = SwitchBlockSidebarSettings; +export type TemplateInputBlock = TemplateInputBlockSidebarSettings; +export type TextareaBlock = TextareaBlockSidebarSettings; +export type ValueOrPromisedValue = ValueOrPromisedValueSidebarSettings; + +export type ContentAreaWidthChoice = `${number}${'px' | '%' | 'vw'}`; +export type ContentAreaWidthTemplateSettings = { + contentAreaWidthChoice?: ContentAreaWidthChoice; + contentAreaWidthCustom?: `${number}${string}`; + contentAreaWidthCustomEnabled?: boolean; +}; +export type ContentAreaPaddingChoice = `${number}${'px' | '%' | 'vw'}`; +export type ContentAreaPaddingTemplateSettings = { + contentAreaPaddingChoice?: ContentAreaPaddingChoice; + contentAreaPaddingCustom?: `${number}${string}`; + contentAreaPaddingCustomEnabled?: boolean; +}; +export type ContentAreaAlignmentChoice = 'left' | 'center' | 'right'; +export type ContentAreaAlignmentSetting = { contentAreaAlignmentChoice?: ContentAreaAlignmentChoice }; + +export type ThemeSettingsStructureExport = { [customSectionName: string]: SettingBlock[] }; +export type ThemeSettingsStructure = Record; + +export type ThemeProps = { + appBridge: AppBridgeTheme; + Content: FC; + Footer: FC | null; +}; + +export type ThemeTemplateExport = { + component: FC; + settings: ThemeSettingsStructureExport; +}; + +export type ThemeConfigExport = { + templates: { + cover: { default: ThemeTemplateExport } & Record; + documentPage: { default: ThemeTemplateExport } & Record; + library: { default: ThemeTemplateExport } & Record; + }; +}; + +/** + * Type helper to make it easier to export a theme, accepts a direct {@link ThemeConfigExport} object. + */ +export const defineTheme = (config: ThemeConfigExport): ThemeConfigExport => config; + +/** + * Type helper to make it easier to export theme's settings structure, accepts a direct {@link ThemeSettingsStructureExport} object. + */ +export const defineSettings = (settingsStructure: ThemeSettingsStructureExport): ThemeSettingsStructureExport => + settingsStructure; diff --git a/packages/guideline-themes/src/utilities/color/color.spec.ts b/packages/guideline-themes/src/utilities/color/color.spec.ts new file mode 100644 index 000000000..504caa867 --- /dev/null +++ b/packages/guideline-themes/src/utilities/color/color.spec.ts @@ -0,0 +1,39 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { describe, expect, it } from 'vitest'; + +import { rgbObjectToRgbString } from './color'; + +describe('Color', () => { + describe('rgbObjectToRgbString', () => { + it('should return a valid RGB string when given an object with R, G, and B.', () => { + const color = { red: 255, green: 99, blue: 71 }; + expect(rgbObjectToRgbString(color)).toBe('rgb(255, 99, 71)'); + }); + + it('should return a valid RGBA string when given an object with R, G, B, and A.', () => { + const color = { red: 255, green: 99, blue: 71, alpha: 0.5 }; + expect(rgbObjectToRgbString(color)).toBe('rgba(255, 99, 71, 0.5)'); + }); + + it('should return a valid RGB string when given an object with 255 R, G, and B.', () => { + const color = { red: 255, green: 255, blue: 255 }; + expect(rgbObjectToRgbString(color)).toBe('rgb(255, 255, 255)'); + }); + + it('should return a valid RGB string when given an object with 0 R, G, and B.', () => { + const color = { red: 0, green: 0, blue: 0 }; + expect(rgbObjectToRgbString(color)).toBe('rgb(0, 0, 0)'); + }); + + it('should return a valid RGBA string when given an object with 255 R, G, B and a valid A.', () => { + const color = { red: 255, green: 255, blue: 255, alpha: 1 }; + expect(rgbObjectToRgbString(color)).toBe('rgba(255, 255, 255, 1)'); + }); + + it('should return a valid RGBA string when given an object with 0 R, G, B and a valid A.', () => { + const color = { red: 0, green: 0, blue: 0, alpha: 1 }; + expect(rgbObjectToRgbString(color)).toBe('rgba(0, 0, 0, 1)'); + }); + }); +}); diff --git a/packages/guideline-themes/src/utilities/color/color.ts b/packages/guideline-themes/src/utilities/color/color.ts new file mode 100644 index 000000000..0cb0f39f2 --- /dev/null +++ b/packages/guideline-themes/src/utilities/color/color.ts @@ -0,0 +1,9 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export const rgbObjectToRgbString = (color: { red: number; green: number; blue: number; alpha?: number }): string => { + if ('alpha' in color) { + return `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha})`; + } + + return `rgb(${color.red}, ${color.green}, ${color.blue})`; +}; diff --git a/packages/guideline-themes/src/utilities/color/index.ts b/packages/guideline-themes/src/utilities/color/index.ts new file mode 100644 index 000000000..6f3fbde03 --- /dev/null +++ b/packages/guideline-themes/src/utilities/color/index.ts @@ -0,0 +1,3 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './color'; diff --git a/packages/guideline-themes/src/utilities/index.ts b/packages/guideline-themes/src/utilities/index.ts new file mode 100644 index 000000000..d52013bdf --- /dev/null +++ b/packages/guideline-themes/src/utilities/index.ts @@ -0,0 +1,4 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +export * from './color'; +export * from './merge'; diff --git a/packages/guideline-themes/src/utilities/merge.ts b/packages/guideline-themes/src/utilities/merge.ts new file mode 100644 index 000000000..ef4aad2c5 --- /dev/null +++ b/packages/guideline-themes/src/utilities/merge.ts @@ -0,0 +1,10 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +/** + * Cleans and joins an array of inputs with possible undefined or boolean values. + * + * @param classNames Array of class names + * @returns Clean string to be used for class name + */ +export const merge = (classNames: (string | undefined | null | boolean)[]): string => + classNames.filter(Boolean).join(' '); diff --git a/packages/guideline-themes/tsconfig.json b/packages/guideline-themes/tsconfig.json new file mode 100644 index 000000000..f0f1cc947 --- /dev/null +++ b/packages/guideline-themes/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2021", + "useDefineForClassFields": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "types": ["node"] + }, + "include": ["src", "setupTests.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/guideline-themes/tsconfig.node.json b/packages/guideline-themes/tsconfig.node.json new file mode 100644 index 000000000..0e59d96dd --- /dev/null +++ b/packages/guideline-themes/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "lib": ["ESNext"], + "target": "ESNext" + }, + "include": [".eslintrc.cjs", "vite.config.ts", "package.json"] +} diff --git a/packages/guideline-themes/vite.config.ts b/packages/guideline-themes/vite.config.ts new file mode 100644 index 000000000..35e00dcbc --- /dev/null +++ b/packages/guideline-themes/vite.config.ts @@ -0,0 +1,58 @@ +/* (c) Copyright Frontify Ltd., all rights reserved. */ + +import { resolve } from 'node:path'; + +import dts from 'vite-plugin-dts'; +import { defineConfig } from 'vitest/config'; + +import { dependencies as dependenciesMap } from './package.json'; + +const dependencies = Object.keys(dependenciesMap); + +export default defineConfig({ + plugins: [dts({ insertTypesEntry: true, rollupTypes: true })], + resolve: { + mainFields: ['module', 'main'], + }, + test: { + environment: 'happy-dom', + deps: { + inline: ['clsx', '@juggle/resize-observer', '@frontify/fondue'], + }, + setupFiles: ['setupTests.ts'], + coverage: { + provider: 'v8', + all: true, + reporter: ['text', 'lcov'], + include: ['src/**/*.ts', 'src/**/*.tsx'], + exclude: ['src/**/test.ts', 'src/**/test.tsx', 'src/**/spec.ts', 'src/**/spec.tsx'], + }, + }, + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + fileName: (format: string) => `[name].${format}.js`, + }, + sourcemap: true, + minify: true, + rollupOptions: { + external: [...dependencies], + output: [ + { + name: 'GuidelineThemes', + format: 'es', + preserveModules: true, + preserveModulesRoot: 'src', + }, + { + name: 'GuidelineThemes', + format: 'umd', + }, + { + name: 'GuidelineThemes', + format: 'cjs', + }, + ], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56531239d..e9df5ea8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,6 +198,63 @@ importers: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.9)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1) + packages/app-bridge-theme: + devDependencies: + '@frontify/eslint-config-react': + specifier: ^0.17.6 + version: 0.17.6(eslint@8.57.0)(prettier@3.3.2)(react@18.3.1)(typescript@5.5.2) + '@testing-library/react': + specifier: ^14.3.0 + version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: ^18.2.75 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.2.24 + version: 18.3.0 + '@vitest/coverage-v8': + specifier: ^1.4.0 + version: 1.4.0(vitest@1.6.0(@types/node@20.14.9)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1)) + '@vitest/ui': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-notice: + specifier: ^0.9.10 + version: 0.9.10(eslint@8.57.0) + happy-dom: + specifier: ^13.10.1 + version: 13.10.1 + prettier: + specifier: ^3.2.5 + version: 3.3.2 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + ts-json-as-const: + specifier: ^1.0.7 + version: 1.0.7(typescript@5.5.2) + type-fest: + specifier: ^4.15.0 + version: 4.20.1 + typescript: + specifier: ^5.4.4 + version: 5.5.2 + vite: + specifier: ^5.2.10 + version: 5.3.1(@types/node@20.14.9)(terser@5.31.1) + vite-plugin-dts: + specifier: ^3.8.1 + version: 3.9.1(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.9)(terser@5.31.1)) + vitest: + specifier: ^1.4.0 + version: 1.6.0(@types/node@20.14.9)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1) + packages/cli: dependencies: '@fastify/cors': @@ -425,6 +482,55 @@ importers: specifier: ^1.4.0 version: 1.6.0(@types/node@18.19.39)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1) + packages/guideline-themes: + dependencies: + '@frontify/app-bridge-theme': + specifier: workspace:^ + version: link:../app-bridge-theme + '@frontify/sidebar-settings': + specifier: ^0.10.1 + version: 0.10.1(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@18.19.39)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2)))(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)) + devDependencies: + '@frontify/eslint-config-react': + specifier: 0.17.6 + version: 0.17.6(eslint@8.57.0)(prettier@3.3.2)(react@18.3.1)(typescript@5.5.2) + '@types/node': + specifier: ^18.19.31 + version: 18.19.39 + '@types/react': + specifier: ^18.2.75 + version: 18.3.3 + '@vitest/coverage-v8': + specifier: ^1.4.0 + version: 1.4.0(vitest@1.6.0(@types/node@18.19.39)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1)) + '@vitest/ui': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-notice: + specifier: ^0.9.10 + version: 0.9.10(eslint@8.57.0) + prettier: + specifier: ^3.2.5 + version: 3.3.2 + typescript: + specifier: ^5.4.4 + version: 5.5.2 + vite: + specifier: ^5.2.10 + version: 5.3.1(@types/node@18.19.39)(terser@5.31.1) + vite-plugin-dts: + specifier: ^3.8.1 + version: 3.9.1(@types/node@18.19.39)(rollup@4.18.0)(typescript@5.5.2)(vite@5.3.1(@types/node@18.19.39)(terser@5.31.1)) + vite-plugin-externals: + specifier: ^0.6.2 + version: 0.6.2(vite@5.3.1(@types/node@18.19.39)(terser@5.31.1)) + vitest: + specifier: ^1.4.0 + version: 1.6.0(@types/node@18.19.39)(@vitest/ui@1.6.0)(happy-dom@13.10.1)(terser@5.31.1) + packages/platform-app: dependencies: '@frontify/sidebar-settings': @@ -1626,6 +1732,12 @@ packages: react: ^18 react-dom: ^18 + '@frontify/sidebar-settings@0.10.1': + resolution: {integrity: sha512-E6qkZaMWGEOfJi6Xy2hINoAE2qlNvIbi35UaLgZx+KjOiYXKTZ/NFIjDrqcHLPsK14fHugFf/oZzDc+qDwtEvA==} + peerDependencies: + react: ^18 + react-dom: ^18 + '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -10391,6 +10503,116 @@ snapshots: react-dom: 18.3.1(react@18.3.1) tailwindcss: 3.4.4(ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2)) + '@frontify/fondue@12.9.3(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@18.19.39)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2)))(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1))': + dependencies: + '@ctrl/tinycolor': 4.1.0 + '@dnd-kit/core': 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/modifiers': 7.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': 8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + '@floating-ui/dom': 1.6.13 + '@frontify/fondue-charts': 5.0.3(@frontify/fondue-tokens@4.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2))))(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@frontify/fondue-components': 16.0.4(@frontify/fondue-icons@0.15.2(react@18.3.1))(@frontify/fondue-tokens@4.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2))))(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2))) + '@frontify/fondue-icons': 0.15.2(react@18.3.1) + '@frontify/fondue-tokens': 4.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2))) + '@popperjs/core': 2.11.8 + '@radix-ui/react-popover': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toolbar': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/accordion': 3.0.0-alpha.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/aria-modal-polyfill': 3.7.13(react@18.3.1) + '@react-aria/breadcrumbs': 3.5.22(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/button': 3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/checkbox': 3.15.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/combobox': 3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/dialog': 3.5.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/interactions': 3.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/link': 3.7.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/listbox': 3.14.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/menu': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/overlays': 3.26.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/radio': 3.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/select': 3.15.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/selection': 3.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/table': 3.17.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/tooltip': 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/visually-hidden': 3.8.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-stately/checkbox': 3.6.12(react@18.3.1) + '@react-stately/collections': 3.12.2(react@18.3.1) + '@react-stately/combobox': 3.10.3(react@18.3.1) + '@react-stately/list': 3.12.0(react@18.3.1) + '@react-stately/menu': 3.9.2(react@18.3.1) + '@react-stately/overlays': 3.6.14(react@18.3.1) + '@react-stately/radio': 3.10.11(react@18.3.1) + '@react-stately/select': 3.6.11(react@18.3.1) + '@react-stately/table': 3.14.0(react@18.3.1) + '@react-stately/toggle': 3.8.2(react@18.3.1) + '@react-stately/tooltip': 3.5.2(react@18.3.1) + '@react-stately/tree': 3.8.8(react@18.3.1) + '@react-types/dialog': 3.5.16(react@18.3.1) + '@react-types/shared': 3.28.0(react@18.3.1) + '@tailwindcss/forms': 0.5.10(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2))) + '@udecode/plate-alignment': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-autoformat': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-basic-marks': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-break': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-code-block': 31.3.4(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-combobox': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-common': 31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-core': 31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-emoji': 31.4.0(patch_hash=qe3otflfq45s3qpehitpsd2wsy)(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-floating': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-indent': 31.1.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-link': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-list': 31.1.3(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-mention': 31.3.5(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-normalizers': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-paragraph': 31.0.0(@udecode/plate-common@31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/plate-utils': 31.3.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/react-utils': 31.0.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@udecode/slate': 31.0.0(slate-history@0.100.0(slate@0.102.0))(slate@0.102.0) + '@udecode/slate-react': 31.0.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-history@0.100.0(slate@0.102.0))(slate-react@0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0))(slate@0.102.0) + '@udecode/slate-utils': 31.3.2(slate-history@0.100.0(slate@0.102.0))(slate@0.102.0) + '@udecode/utils': 31.0.0 + '@udecode/zustood': 2.0.0(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)) + date-fns: 3.6.0 + escape-html: 1.0.3 + framer-motion: 10.18.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + immer: 10.1.1 + is-hotkey: 0.2.0 + lodash-es: 4.17.21 + react: 18.3.1 + react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-datepicker: 6.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dnd: 16.0.1(@types/node@18.19.39)(@types/react@18.3.3)(react@18.3.1) + react-dnd-html5-backend: 16.0.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + react-is: 18.3.1 + react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-textarea-autosize: 8.5.9(@types/react@18.3.3)(react@18.3.1) + remark-gfm: 3.0.1 + remark-parse: 10.0.2 + slate: 0.102.0 + slate-react: 0.102.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate@0.102.0) + unified: 10.1.2 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - '@react-spring/web' + - '@types/hoist-non-react-statics' + - '@types/node' + - '@types/react' + - '@types/react-dom' + - react-native + - scheduler + - slate-history + - slate-hyperscript + - supports-color + - tailwindcss + - zustand + '@frontify/fondue@12.9.3(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@18.19.39)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.26.0)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2)))(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1))': dependencies: '@ctrl/tinycolor': 4.1.0 @@ -10721,6 +10943,25 @@ snapshots: - tailwindcss - zustand + '@frontify/sidebar-settings@0.10.1(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@18.19.39)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2)))(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1))': + dependencies: + '@frontify/fondue': 12.9.3(@react-spring/web@9.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@18.19.39)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)(scheduler@0.24.0-canary-efb381bbf-20230505)(slate-history@0.100.0(slate@0.102.0))(slate-hyperscript@0.100.0(slate@0.102.0))(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@18.19.39)(typescript@5.5.2)))(zustand@4.5.3(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@react-spring/web' + - '@types/hoist-non-react-statics' + - '@types/node' + - '@types/react' + - '@types/react-dom' + - react-native + - scheduler + - slate-history + - slate-hyperscript + - supports-color + - tailwindcss + - zustand + '@hapi/hoek@9.3.0': optional: true