diff --git a/changelog.txt b/changelog.txt
index b24b8294744d68..85fa8cc791aec4 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,451 @@
== Changelog ==
+= 17.6.0-rc.1 =
+
+
+## Changelog
+
+### Features
+
+#### Interactivity API
+- Add `wp-data-on-window` and `wp-data-on-document` directives. ([57931](https://github.com/WordPress/gutenberg/pull/57931))
+- Add `wp-each` directive. ([57859](https://github.com/WordPress/gutenberg/pull/57859))
+- Add `wp-run` directive and `useInit` & `useWatch` hooks. ([57805](https://github.com/WordPress/gutenberg/pull/57805))
+
+#### Typography
+- Add defaultFontSizes option to theme.json. ([56661](https://github.com/WordPress/gutenberg/pull/56661))
+- Font Library: Add wp_get_font_dir() function. ([57730](https://github.com/WordPress/gutenberg/pull/57730))
+
+#### Custom Fields
+- Block Bindings: Disable editing of bound block attributes in editor UI. ([58085](https://github.com/WordPress/gutenberg/pull/58085))
+
+#### Block Editor
+- Add effects/box shadow tools to block inspector. ([57654](https://github.com/WordPress/gutenberg/pull/57654))
+
+
+### Enhancements
+
+- Add gettext content when translating 'Header'. ([51066](https://github.com/WordPress/gutenberg/pull/51066))
+- Disable lock button if user cannot control lock state. ([57274](https://github.com/WordPress/gutenberg/pull/57274))
+- Editor: Unify the Editor Mode preference. ([57642](https://github.com/WordPress/gutenberg/pull/57642))
+- Element: Start reexporting PureComponent. ([58076](https://github.com/WordPress/gutenberg/pull/58076))
+- Live Preview: Show the current theme name on the theme activation modal. ([57588](https://github.com/WordPress/gutenberg/pull/57588))
+- Remove right negative margin from pinned items. ([57666](https://github.com/WordPress/gutenberg/pull/57666))
+- Unify the preferences modal UI between post and site editor. ([57639](https://github.com/WordPress/gutenberg/pull/57639))
+- Update style revision top toolbar text. ([58057](https://github.com/WordPress/gutenberg/pull/58057))
+- Use ClipboardJS latest version and clean up focus loss workaround. ([57156](https://github.com/WordPress/gutenberg/pull/57156))
+
+#### Components
+- Add opt-in prop for 40px default size for `BoxControl`, `BorderControl`, and `BorderBoxControl`. ([56185](https://github.com/WordPress/gutenberg/pull/56185))
+- BorderControl: Replace style picker with ToggleGroupControl. ([57562](https://github.com/WordPress/gutenberg/pull/57562))
+- ColorPicker: Store internal HSLA state for better slider UX. ([57555](https://github.com/WordPress/gutenberg/pull/57555))
+- Migrate PaletteEdit and CircularOptionPicker tests from user-event to ariakit/test. ([57809](https://github.com/WordPress/gutenberg/pull/57809))
+- Replace `TabPanel` with `Tabs` in the Editor Preferences Modal. ([57293](https://github.com/WordPress/gutenberg/pull/57293))
+- Theme: Set `color` on wrapper. ([58095](https://github.com/WordPress/gutenberg/pull/58095))
+- Tooltip: No-op when nested inside another Tooltip component. ([57202](https://github.com/WordPress/gutenberg/pull/57202))
+- `BoxControl`: Update design. ([56665](https://github.com/WordPress/gutenberg/pull/56665))
+
+#### Interactivity API
+- Render the root interactive blocks. ([57729](https://github.com/WordPress/gutenberg/pull/57729))
+- Interactivity Router: Replace `data-wp-navigation-id` with `data-wp-router-region`. ([58191](https://github.com/WordPress/gutenberg/pull/58191))
+- Interactivity: Export `withScope()` and allow to use it with asynchronous operations. ([58013](https://github.com/WordPress/gutenberg/pull/58013))
+- Prevent the use of components in `wp-text`. ([57879](https://github.com/WordPress/gutenberg/pull/57879))
+- Remove wp-data-navigation-link directive. ([57853](https://github.com/WordPress/gutenberg/pull/57853))
+- Server Directive Processing Refactor. ([58066](https://github.com/WordPress/gutenberg/pull/58066))
+- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([57891](https://github.com/WordPress/gutenberg/pull/57891))
+
+#### Block Editor
+- Add copy link button to Link UI. ([58170](https://github.com/WordPress/gutenberg/pull/58170))
+- Improve LinkControl preview. ([57775](https://github.com/WordPress/gutenberg/pull/57775))
+- Keep Link UI open upon initial link creation when used in RichText. ([57726](https://github.com/WordPress/gutenberg/pull/57726))
+- List View: Displace list view items when dragging (a bit more WYSIWYG). ([56625](https://github.com/WordPress/gutenberg/pull/56625))
+- Show initial suggestions in rich text Link UI. ([57743](https://github.com/WordPress/gutenberg/pull/57743))
+
+#### Typography
+- Font Library Modal: Reset the selected font when installing a new font. ([57817](https://github.com/WordPress/gutenberg/pull/57817))
+- Font Library: Disable font library UI using a PHP filter. ([57818](https://github.com/WordPress/gutenberg/pull/57818))
+- Font Library: Filter fonts upload directory. ([57697](https://github.com/WordPress/gutenberg/pull/57697))
+- Font Library: Use data or src file to define font collection data. ([57734](https://github.com/WordPress/gutenberg/pull/57734))
+
+#### Block Library
+- Add more taxonomy options to the post navigation link. ([48912](https://github.com/WordPress/gutenberg/pull/48912))
+- Add: Footnotes support for other CPT's. ([57353](https://github.com/WordPress/gutenberg/pull/57353))
+- Better navigation link variations for post types / taxonomies. ([56100](https://github.com/WordPress/gutenberg/pull/56100))
+- Remove "blocks" from copy and delete labels. ([57769](https://github.com/WordPress/gutenberg/pull/57769))
+
+#### Data Views
+- DataViews: Enable grid layout for templates & parts by default. ([58137](https://github.com/WordPress/gutenberg/pull/58137))
+- DataViews: Make dataviews powered page patterns stable. ([58139](https://github.com/WordPress/gutenberg/pull/58139))
+- DataViews: Make the "Manage Pages" stable. ([58166](https://github.com/WordPress/gutenberg/pull/58166))
+
+#### Site Editor
+- Group templates in sidebar list. ([57711](https://github.com/WordPress/gutenberg/pull/57711))
+- Initial routing refactoring to separate preview from list view. ([57938](https://github.com/WordPress/gutenberg/pull/57938))
+- Iterate on warning text for block removal for query/post template/post content. ([58138](https://github.com/WordPress/gutenberg/pull/58138))
+
+#### Block API
+- Block Bindings: Update source registration syntax and remove APIs that should be private. ([58205](https://github.com/WordPress/gutenberg/pull/58205))
+- Block Hooks: Do not remove toggle if hooked block is present elsewhere. ([57928](https://github.com/WordPress/gutenberg/pull/57928))
+
+#### Synced Patterns
+- Add basic pattern overrides end-to-end tests. ([57792](https://github.com/WordPress/gutenberg/pull/57792))
+- Use a patch format and support `linkTarget` of `core/button` for Pattern Overrides. ([58165](https://github.com/WordPress/gutenberg/pull/58165))
+
+#### Patterns
+- Add image block support for pattern overrides. ([57909](https://github.com/WordPress/gutenberg/pull/57909))
+- Outline editable blocks that are within a content-locked container. ([57901](https://github.com/WordPress/gutenberg/pull/57901))
+
+#### Post Editor
+- Post Lock: Use the new modal size preset. ([58197](https://github.com/WordPress/gutenberg/pull/58197))
+
+#### Font Library
+- Update the default collection data URL to the wordpress.org cdn. ([58186](https://github.com/WordPress/gutenberg/pull/58186))
+
+#### Commands
+- Minor command tweaks. ([58148](https://github.com/WordPress/gutenberg/pull/58148))
+
+#### Extensibility
+- Update Navigation block to render hooked inner blocks. ([57754](https://github.com/WordPress/gutenberg/pull/57754))
+
+#### Global Styles
+- Site editor: Add global styles changes to save flow. ([57470](https://github.com/WordPress/gutenberg/pull/57470))
+
+#### Design Tools
+- Pullquote Block: Add padding and margin support. ([45731](https://github.com/WordPress/gutenberg/pull/45731))
+
+
+### New APIs
+
+#### Block API
+- Block Bindings API: Add block bindings PHP registration mechanisms and "Post meta" source under the experimental flag. ([57249](https://github.com/WordPress/gutenberg/pull/57249))
+- Block Bindings API: Refactor logic into Block Bindings class and singleton pattern. ([57742](https://github.com/WordPress/gutenberg/pull/57742))
+
+
+### Bug Fixes
+
+- (editor)(fix) Append the `edit-post-header-toolbar` class in NavigableToolbar for backward compatibility with plugin GUI injections. ([58154](https://github.com/WordPress/gutenberg/pull/58154))
+- Bring back the chevron. ([57807](https://github.com/WordPress/gutenberg/pull/57807))
+- Fix flaky "create a new pattern" test. ([57747](https://github.com/WordPress/gutenberg/pull/57747))
+- Fix site editor layout regressions. ([58077](https://github.com/WordPress/gutenberg/pull/58077))
+- Preferences: Add a proxy to retrieve the deprecated preferences with a deprecation message. ([58016](https://github.com/WordPress/gutenberg/pull/58016))
+- Remove unused argument from sprintf in pagination.js. ([57823](https://github.com/WordPress/gutenberg/pull/57823))
+- core-js: Only polyfill stable features. ([57674](https://github.com/WordPress/gutenberg/pull/57674))
+
+#### Block Library
+- Avatar block: Fix broken aligments in the editor. ([58114](https://github.com/WordPress/gutenberg/pull/58114))
+- Embed Block: Fix retry processing when embedding with trailing slash fails. ([58007](https://github.com/WordPress/gutenberg/pull/58007))
+- Lightbox: Fix "Expand on click" control being disabled unintentionally. ([56053](https://github.com/WordPress/gutenberg/pull/56053))
+- Modified Date Block: Don't render change date tool. ([57914](https://github.com/WordPress/gutenberg/pull/57914))
+- Only prioritise Quote transform where relevant. ([57749](https://github.com/WordPress/gutenberg/pull/57749))
+- Query Loop: Fix posts list variation detection. ([58194](https://github.com/WordPress/gutenberg/pull/58194))
+
+#### Components
+- Button: Always render the Tooltip component even when a tooltip should not be shown. ([56490](https://github.com/WordPress/gutenberg/pull/56490))
+- CustomSelect: Adjust `renderSelectedValue` to fix sizing. ([57865](https://github.com/WordPress/gutenberg/pull/57865))
+- ToggleGroupControl: Improve controlled value detection. ([57770](https://github.com/WordPress/gutenberg/pull/57770))
+- Tooltip: Accept specific tooltip props. ([58125](https://github.com/WordPress/gutenberg/pull/58125))
+- Tooltip: Forward and merge inner tooltip props correctly. ([57878](https://github.com/WordPress/gutenberg/pull/57878))
+
+#### Data Views
+- DataViews: Default sort order in templates by title. ([58175](https://github.com/WordPress/gutenberg/pull/58175))
+- DataViews: Don't always display horizontal scrollbar. ([58101](https://github.com/WordPress/gutenberg/pull/58101))
+- DataViews: Fix author sorting in templates and template parts. ([58167](https://github.com/WordPress/gutenberg/pull/58167))
+
+#### Patterns
+- Add black border back when editing synced pattern in the post editor. ([57631](https://github.com/WordPress/gutenberg/pull/57631))
+- Outline editable blocks when in a pattern that has locked children. ([57991](https://github.com/WordPress/gutenberg/pull/57991))
+- Remove text align control for paragraph, heading, and button in contentOnly editing mode. ([57906](https://github.com/WordPress/gutenberg/pull/57906))
+
+#### List View
+- Image Block: Make block name affect list view. ([57955](https://github.com/WordPress/gutenberg/pull/57955))
+- More Block: Make block name affect list view. ([58160](https://github.com/WordPress/gutenberg/pull/58160))
+
+#### Block API
+- Block Hooks: Fix toggle. ([57956](https://github.com/WordPress/gutenberg/pull/57956))
+- Fix formats not working in block bindings content. ([58055](https://github.com/WordPress/gutenberg/pull/58055))
+
+#### Global Styles
+- Correctly decode border color values. ([57876](https://github.com/WordPress/gutenberg/pull/57876))
+- Fix: Theme.json application of custom root selector for styles. ([58050](https://github.com/WordPress/gutenberg/pull/58050))
+
+#### Data Layer
+- Data: Allow binding registry selector to multiple registries. ([57943](https://github.com/WordPress/gutenberg/pull/57943))
+- Data: Fix memoized createRegistrySelector. ([57888](https://github.com/WordPress/gutenberg/pull/57888))
+
+#### Typography
+- #56734 When there is no font, the border should not appear. Display further guidance text. ([56825](https://github.com/WordPress/gutenberg/pull/56825))
+- Fluid typography: Do not calculate fluid font size when min and max viewport widths are equal. ([57866](https://github.com/WordPress/gutenberg/pull/57866))
+
+#### Block Editor
+- Fix regression: Content locking does not stops when an outside block is selected. ([57737](https://github.com/WordPress/gutenberg/pull/57737))
+- LinkControl: Remove unnecessary right padding of input fields. ([57784](https://github.com/WordPress/gutenberg/pull/57784))
+
+#### Custom Fields
+- Block Bindings: Fix button popover not showing in patterns. ([58219](https://github.com/WordPress/gutenberg/pull/58219))
+
+#### Font Library
+- Fix typo. ([58193](https://github.com/WordPress/gutenberg/pull/58193))
+
+#### Synced Patterns
+- Fix losing overrides after detaching patterns. ([58164](https://github.com/WordPress/gutenberg/pull/58164))
+
+#### Interactivity API
+- Prevent `wp-data-on=""` from creating `onDefault` handlers. ([57925](https://github.com/WordPress/gutenberg/pull/57925))
+
+#### CSS & Styling
+- Styles revisions: Remove body padding. ([57748](https://github.com/WordPress/gutenberg/pull/57748))
+
+#### Templates API
+- Fix visual indication of switch to default template in the post editor. ([57718](https://github.com/WordPress/gutenberg/pull/57718))
+
+
+### Accessibility
+
+#### Site Editor
+- Fix font variants count color contrast ratio and l10n. ([58117](https://github.com/WordPress/gutenberg/pull/58117))
+- Make the site hub View Site link always visible. ([57423](https://github.com/WordPress/gutenberg/pull/57423))
+
+#### Block Editor
+- Fix parent selector button focus style and metrics. ([57728](https://github.com/WordPress/gutenberg/pull/57728))
+- Restore visual separator between mover buttons when show button label is on. ([57640](https://github.com/WordPress/gutenberg/pull/57640))
+
+#### Widgets Editor
+- Fix Widgets page Undo and Redo accessibility and keyboard interaction. ([57677](https://github.com/WordPress/gutenberg/pull/57677))
+
+
+### Performance
+
+- Add patterns load test. ([57828](https://github.com/WordPress/gutenberg/pull/57828))
+- Block editor: Avoid list re-rendering on select. ([57188](https://github.com/WordPress/gutenberg/pull/57188))
+- Block editor: Don't register shortcuts for preview editors. ([57984](https://github.com/WordPress/gutenberg/pull/57984))
+- Block editor: Fix performance regression after #57950. ([57971](https://github.com/WordPress/gutenberg/pull/57971))
+- Block editor: Use context for useBlockEditingMode. ([57950](https://github.com/WordPress/gutenberg/pull/57950))
+- BlockSwitcher: Defer transform calculations until the Dropdown is open. ([57892](https://github.com/WordPress/gutenberg/pull/57892))
+- Call variation through callback so it's only loaded when needed - in support of trac 59969. ([56952](https://github.com/WordPress/gutenberg/pull/56952))
+- Editor styles: Cache transform. ([57810](https://github.com/WordPress/gutenberg/pull/57810))
+- Footnotes: Combine format store subscription. ([58129](https://github.com/WordPress/gutenberg/pull/58129))
+- Iframe: Calc compat styles once per page load. ([57798](https://github.com/WordPress/gutenberg/pull/57798))
+- Measure typing with the top toolbar enabled. ([57709](https://github.com/WordPress/gutenberg/pull/57709))
+- Meta boxes: Don't initialise if there are none. ([57182](https://github.com/WordPress/gutenberg/pull/57182))
+- Patterns: Avoid fetching on load. ([57999](https://github.com/WordPress/gutenberg/pull/57999))
+- Site editor: Avoid fetching themes on load. ([57985](https://github.com/WordPress/gutenberg/pull/57985))
+- Site editor: Reduce artificial loading delay from 1s to 100ms. ([57953](https://github.com/WordPress/gutenberg/pull/57953))
+- Site editor: Remove store subscription per block. ([57995](https://github.com/WordPress/gutenberg/pull/57995))
+- Template Part & Query: Avoid server requests on mount. ([57987](https://github.com/WordPress/gutenberg/pull/57987))
+- Template part block: Avoid parsing ALL patterns on mount. ([57856](https://github.com/WordPress/gutenberg/pull/57856))
+
+#### Block Editor
+- Revert "Block editor: Avoid list re-rendering on select". ([58147](https://github.com/WordPress/gutenberg/pull/58147))
+
+#### Post Editor
+- Editor: Use hooks instead of HoCs for `EditorNotices`. ([57772](https://github.com/WordPress/gutenberg/pull/57772))
+
+
+### Experiments
+
+#### Data Views
+- Add: Bulk actions to dataviews with the new design. ([57255](https://github.com/WordPress/gutenberg/pull/57255))
+- Data view list layout: Fix thumbnail dimensions. ([57774](https://github.com/WordPress/gutenberg/pull/57774))
+- Data views table layout: Update cell vertical alignment. ([57804](https://github.com/WordPress/gutenberg/pull/57804))
+- DataViews: Add description to pages. ([57793](https://github.com/WordPress/gutenberg/pull/57793))
+- DataViews: Add front page to pages page sidebar. ([57759](https://github.com/WordPress/gutenberg/pull/57759))
+- DataViews: Better management of `layout` param in templates. ([58116](https://github.com/WordPress/gutenberg/pull/58116))
+- DataViews: Make list layout the default for templates with the experiment enabled. ([57933](https://github.com/WordPress/gutenberg/pull/57933))
+- DataViews: Revert list view as default for pages. ([58081](https://github.com/WordPress/gutenberg/pull/58081))
+- DataViews: Revert list view as default for templates. ([58079](https://github.com/WordPress/gutenberg/pull/58079))
+- DataViews: Set primary field styles. ([57846](https://github.com/WordPress/gutenberg/pull/57846))
+- DataViews: Show loading / no result message for the list layout. ([57764](https://github.com/WordPress/gutenberg/pull/57764))
+- DataViews: Update template parts view. ([57952](https://github.com/WordPress/gutenberg/pull/57952))
+- DataViews: Use button for patterns, pages and templates preview field. ([58071](https://github.com/WordPress/gutenberg/pull/58071))
+- DataViews: Use table layout for templates when experiment disabled. ([57960](https://github.com/WordPress/gutenberg/pull/57960))
+- Stabilise view options button icon. ([57964](https://github.com/WordPress/gutenberg/pull/57964))
+- Update Grid layout design. ([57880](https://github.com/WordPress/gutenberg/pull/57880))
+- Update Pages preview field display. ([57919](https://github.com/WordPress/gutenberg/pull/57919))
+- Update Templates table layout. ([57930](https://github.com/WordPress/gutenberg/pull/57930))
+- Update: Show template sources on templates Dataviews sidebar. ([58124](https://github.com/WordPress/gutenberg/pull/58124))
+
+#### Synced Patterns
+- Add a control to reset pattern overrides. ([57845](https://github.com/WordPress/gutenberg/pull/57845))
+- Allow heading and button in Pattern Overrides. ([57789](https://github.com/WordPress/gutenberg/pull/57789))
+
+#### Typography
+- Download then upload font face assets when installing from a collection. ([57694](https://github.com/WordPress/gutenberg/pull/57694))
+- Use `slug` instead of `id` for Font Collection. ([57735](https://github.com/WordPress/gutenberg/pull/57735))
+
+#### REST API
+- Font Library Refactor. ([57688](https://github.com/WordPress/gutenberg/pull/57688))
+
+#### Block Editor
+- Allow drag and drop to create Rows and Galleries. ([56186](https://github.com/WordPress/gutenberg/pull/56186))
+
+
+### Documentation
+
+- Add a video demonstration to the Quick Start Guide. ([57834](https://github.com/WordPress/gutenberg/pull/57834))
+- Button: Improve `disabled`-related prop descriptions. ([57864](https://github.com/WordPress/gutenberg/pull/57864))
+- Components: Move CHANGELOG entries under the correct release. ([57885](https://github.com/WordPress/gutenberg/pull/57885))
+- Docs: Fix typo in "The block wrapper" document. ([58106](https://github.com/WordPress/gutenberg/pull/58106))
+- Docs: Use 'key' in 'editor.BlockEdit' filter code examples. ([58119](https://github.com/WordPress/gutenberg/pull/58119))
+- Document files/directories requiring backmerging to WP Core for major release. ([58064](https://github.com/WordPress/gutenberg/pull/58064))
+- Fix the iframe markup of the embed video in the Quick Start Guide. ([57857](https://github.com/WordPress/gutenberg/pull/57857))
+- Fix: Link to the nodejs release page. ([57816](https://github.com/WordPress/gutenberg/pull/57816))
+- Fix: Typo on BlockListBlock comments. ([57814](https://github.com/WordPress/gutenberg/pull/57814))
+- Fix: Typos on __unstableSetTemporarilyEditingAsBlocks documentation. ([57768](https://github.com/WordPress/gutenberg/pull/57768))
+- Font Library: Add font collection JSON schema. ([57736](https://github.com/WordPress/gutenberg/pull/57736))
+- Prefixes all php filters with wpdocs_. ([53914](https://github.com/WordPress/gutenberg/pull/53914))
+- Remove the unnecessary TOC and fix grammar/formatting in the Patterns doc. ([57825](https://github.com/WordPress/gutenberg/pull/57825))
+- Remove the 👋 emoji from the Block Editor Handbook. ([58023](https://github.com/WordPress/gutenberg/pull/58023))
+- Update versions-in-wordpress.md. ([57916](https://github.com/WordPress/gutenberg/pull/57916))
+- [Type] Developer Documentation - Fix removeAllNotices dispatch on the removeAllNotices doc section of @wordpress/notices. ([57436](https://github.com/WordPress/gutenberg/pull/57436))
+
+
+### Code Quality
+
+- Block Renaming - move backported WP 6.5 code to 6.5 compat dir. ([58126](https://github.com/WordPress/gutenberg/pull/58126))
+- Fix comments block. ([57820](https://github.com/WordPress/gutenberg/pull/57820))
+- Fon Library: Remove 'version' property from font collection schema. ([58025](https://github.com/WordPress/gutenberg/pull/58025))
+- Remove unneeded `margin: 0` override for `Notice` component consumer. ([57794](https://github.com/WordPress/gutenberg/pull/57794))
+- Rename __experimentalGetGlobalBlocksByName to getBlocksByName. ([58156](https://github.com/WordPress/gutenberg/pull/58156))
+- Scripts: Remove unused variable in bin/list-experimental-api-matches.sh. ([57771](https://github.com/WordPress/gutenberg/pull/57771))
+- Shadows: Prevent empty style object when removing shadow. ([58155](https://github.com/WordPress/gutenberg/pull/58155))
+- [Fonts API] removing files and files loading no longer needed. ([57972](https://github.com/WordPress/gutenberg/pull/57972))
+
+#### Components
+- PaletteEdit: Improve unit tests. ([57645](https://github.com/WordPress/gutenberg/pull/57645))
+- Tooltip and Button: Tidy up unit tests. ([57975](https://github.com/WordPress/gutenberg/pull/57975))
+- Tooltip: Add test for classname leakage. ([58182](https://github.com/WordPress/gutenberg/pull/58182))
+
+#### Block Editor
+- Soft deprecate custom 'pure' HoC in favor of 'React.memo'. ([57173](https://github.com/WordPress/gutenberg/pull/57173))
+- Stabilise RecursionProvider and useHasRecursion APIs. ([58120](https://github.com/WordPress/gutenberg/pull/58120))
+- Tidy up block patterns selectors. ([57913](https://github.com/WordPress/gutenberg/pull/57913))
+
+#### Block Library
+- Gallery Block: Remove duplicate return statement. ([57746](https://github.com/WordPress/gutenberg/pull/57746))
+- Navigation: Move the renderer class to the main navigation file. ([57979](https://github.com/WordPress/gutenberg/pull/57979))
+
+#### Font Library
+- Remove WP_Font_Family class that is no longer used. ([58184](https://github.com/WordPress/gutenberg/pull/58184))
+
+#### Block Directory
+- DownloadableBlocksPanel: Remove withSelect in favor of useSelect. ([58109](https://github.com/WordPress/gutenberg/pull/58109))
+
+#### Patterns
+- Stabilize the pattern overrides block context. ([58102](https://github.com/WordPress/gutenberg/pull/58102))
+
+#### Block API
+- Block Bindings: Remove the experimental flag. ([58089](https://github.com/WordPress/gutenberg/pull/58089))
+
+#### Post Editor
+- Editor: Use hooks instead of HoCs in 'PostScheduleCheck'. ([57833](https://github.com/WordPress/gutenberg/pull/57833))
+
+#### Script Modules API
+- Update the code and move it to the compat/wordpress-6.5 folder. ([57778](https://github.com/WordPress/gutenberg/pull/57778))
+
+#### Data Views
+- Remove obsolete check from dataviews modal actions title. ([57753](https://github.com/WordPress/gutenberg/pull/57753))
+
+
+### Tools
+
+- (chore) Revert bump to the v17.5.1 (draft) due to bug in the release found by manual testing. ([58027](https://github.com/WordPress/gutenberg/pull/58027))
+- Automate creation of Issue for major release PHP synchronisation. ([57890](https://github.com/WordPress/gutenberg/pull/57890))
+- Fix misplaced ReactRefreshWebpackPlugin. ([57777](https://github.com/WordPress/gutenberg/pull/57777))
+
+#### Testing
+- Add `setGutenbergExperiments` to `requestUtils`. ([56663](https://github.com/WordPress/gutenberg/pull/56663))
+- Add: End to end test to content locking stop editing as blocks behavior. ([57812](https://github.com/WordPress/gutenberg/pull/57812))
+- Attempt to fix php unit tests (variations api change). ([58090](https://github.com/WordPress/gutenberg/pull/58090))
+- Migrate 'block grouping' end-to-end tests to Playwright. ([57684](https://github.com/WordPress/gutenberg/pull/57684))
+- Migrate 'embedding' end-to-end tests to Playwright. ([57969](https://github.com/WordPress/gutenberg/pull/57969))
+- Migrate 'typewriter' end-to-end tests to Playwright. ([57673](https://github.com/WordPress/gutenberg/pull/57673))
+- Remove unused Navigation block end-to-end test fixtures. ([57848](https://github.com/WordPress/gutenberg/pull/57848))
+
+#### Build Tooling
+- Update caniuse-lite package. ([58087](https://github.com/WordPress/gutenberg/pull/58087))
+- Update the cherry pick script to work with the new version of gh. ([57917](https://github.com/WordPress/gutenberg/pull/57917))
+
+
+### Various
+
+- Interactivity API: Fix data-wp-on-document flaky test. ([58008](https://github.com/WordPress/gutenberg/pull/58008))
+- Interactivity API: Fix flaky test on-window. ([58134](https://github.com/WordPress/gutenberg/pull/58134))
+- Pattern Categories: Fix capitalization. ([58112](https://github.com/WordPress/gutenberg/pull/58112))
+- Remove check-latest-npm validation. ([57797](https://github.com/WordPress/gutenberg/pull/57797))
+
+#### Interactivity API
+- Create `@wordpress/interactivity-router` module. ([57924](https://github.com/WordPress/gutenberg/pull/57924))
+- Fix flaky test on-window, remove duplicate expect on-document. ([58181](https://github.com/WordPress/gutenberg/pull/58181))
+- Remove `data-wp-slot` and `data-wp-fill`. ([57854](https://github.com/WordPress/gutenberg/pull/57854))
+- Remove unused `state` and rename `props` to `attributes` in `getElement()`. ([57974](https://github.com/WordPress/gutenberg/pull/57974))
+
+#### Patterns
+- Remove pattern override experiment completely. ([58105](https://github.com/WordPress/gutenberg/pull/58105))
+- Update pattern overrides to use a hard coded support array. ([57912](https://github.com/WordPress/gutenberg/pull/57912))
+
+#### Data Views
+- Dataviews: Add Bulk actions to page. ([57826](https://github.com/WordPress/gutenberg/pull/57826))
+
+#### Post Editor
+- Add description to the save panel header when nothing is checked. ([57716](https://github.com/WordPress/gutenberg/pull/57716))
+
+#### HTML API
+- Backport updates from Core. ([57022](https://github.com/WordPress/gutenberg/pull/57022))
+
+#### Block Editor
+- Video Block: Add raw transformation from `video` html. ([47159](https://github.com/WordPress/gutenberg/pull/47159))
+
+
+## First time contributors
+
+The following PRs were merged by first time contributors:
+
+- @kt-12: Call variation through callback so it's only loaded when needed - in support of trac 59969. ([56952](https://github.com/WordPress/gutenberg/pull/56952))
+- @leomuniz: [Type] Developer Documentation - Fix removeAllNotices dispatch on the removeAllNotices doc section of @wordpress/notices. ([57436](https://github.com/WordPress/gutenberg/pull/57436))
+
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@aaronrobertshaw @afercia @ajlende @andrewserong @annezazu @artemiomorales @arthur791004 @atachibana @bacoords @bph @brookewp @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @creativecoder @DAreRodz @dcalhoun @derekblank @dmsnell @draganescu @ecgan @ellatrix @fluiddot @fullofcaffeine @gaambo @geriux @getdave @glendaviesnz @gonzomir @inc2734 @jameskoster @jeryj @jffng @jorgefilipecosta @jsnajdr @kevin940726 @kt-12 @leomuniz @luisherranz @MaggieCabrera @Mamaduka @matiasbenedetto @mcsf @michalczaplinski @mikachan @mirka @ndiego @noisysocks @ntsekouras @oandregal @ockham @oguzkocer @pbking @ramonjd @richtabor @SantosGuillamot @scruffian @SiobhyB @sirreal @swissspidy @t-hamano @talldan @tellthemachines @tjcafferkey @tyxla @vcanales @youknowriad
+
+
+= 17.5.2 =
+
+## Changelog
+
+### Bug Fixes
+
+- (Preferences)(17.5.1)(fix) Remove non-core-migrated preferences from the deprecation proxy for `get` ([58153](https://github.com/WordPress/gutenberg/pull/58153))
+- (editor)(fix) Append the `edit-post-header-toolbar` class in NavigableToolbar for backward compatibility with plugin GUI injections ([58154](https://github.com/WordPress/gutenberg/pull/58154))
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@fullofcaffeine
+
+
+
+
+= 17.5.1 =
+
+## Changelog
+
+### Bug Fixes
+
+- (Preferences)(hotfix)(17.5) Hotfix for missing preferences in the `core` scope([58031](https://github.com/WordPress/gutenberg/pull/58031))
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@youknowriad @fullofcaffeine
+
+
+
+
= 17.5.0 =
## Changelog
diff --git a/docs/contributors/code/back-merging-to-wp-core.md b/docs/contributors/code/back-merging-to-wp-core.md
new file mode 100644
index 00000000000000..2b1ec77df1e550
--- /dev/null
+++ b/docs/contributors/code/back-merging-to-wp-core.md
@@ -0,0 +1,31 @@
+# Back-merging code to WordPress Core
+
+For major releases of the WordPress software, Gutenberg features need to be merged into WordPress Core. Typically this involves taking changes made in `.php` files within the Gutenberg repository and making the equivalent updates in the WP Core codebase.
+
+## Files/Directories
+
+Changes to files within the following files/directories will typically require back-merging to WP Core:
+
+- `lib/`
+- `phpunit/`
+
+## Ignored directories/files
+
+The following directories/files do _not_ require back-merging to WP Core:
+
+- `lib/load.php` - Plugin specific code.
+- `lib/experiments-page.php` - experiments are Plugin specific.
+- `packages/block-library` - this is handled automatically during the packages sync process.
+- `packages/e2e-tests/plugins` - PHP files related to e2e tests only. Mostly fixture data generators.
+- `phpunit/blocks` - the code is maintained in Gutenberg so the test should be as well.
+
+Please note this list is not exhaustive.
+
+## Pull Request Criteria
+
+In general, all PHP code committed to the Gutenberg repository since the date of the final Gutenberg release that was included in [the _last_ stable WP Core release](https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/) should be considered for back merging to WP Core.
+
+There are however certain exceptions to that rule. PRs with the following criteria do _not_ require back-merging to WP Core:
+
+- Does not contain changes to PHP code.
+- Has label `Backport from WordPress Core` - this code is already in WP Core.
diff --git a/docs/getting-started/fundamentals/block-wrapper.md b/docs/getting-started/fundamentals/block-wrapper.md
index 1f2404eca9b031..cf588cf33cf6f8 100644
--- a/docs/getting-started/fundamentals/block-wrapper.md
+++ b/docs/getting-started/fundamentals/block-wrapper.md
@@ -2,7 +2,7 @@
Each block's markup is wrapped by a container HTML tag that needs to have the proper attributes to fully work in the Block Editor and to reflect the proper block's style settings when rendered in the Block Editor and the front end. As developers, we have full control over the block's markup, and WordPress provides the tools to add the attributes that need to exist on the wrapper to our block's markup.
-Ensuring proper attributes to the block wrapper is especially important when using custom styling or features like `supports`.
+Ensuring proper attributes to the block wrapper is especially important when using custom styling or features like `supports`.
The use of
supports
generates a set of properties that need to be manually added to the wrapping element of the block so they're properly stored as part of the block data.
@@ -10,8 +10,8 @@ The use of
supports
generates a set of properties that need to be m
A block can have three sets of markup defined, each one of them with a specific target and purpose:
-- The one for the **Block Editor**, defined through a `edit` React component passed to [`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#registerblocktype) when registering the block in the client.
-- The one used to **save the block in the DB**, defined through a `save` function passed to `registerBlockType` when registering the block in the client.
+- The one for the **Block Editor**, defined through a `edit` React component passed to [`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#registerblocktype) when registering the block in the client.
+- The one used to **save the block in the DB**, defined through a `save` function passed to `registerBlockType` when registering the block in the client.
- This markup will be returned to the front end on request if no dynamic render has been defined for the block.
- The one used to **dynamically render the markup of the block** returned to the front end on request, defined through the `render_callback` on [`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/) or the [`render`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#render) PHP file in `block.json`
- If defined, this server-side generated markup will be returned to the front end, ignoring the markup stored in DB.
@@ -21,13 +21,13 @@ For the [`edit` React component and the `save` function](https://developer.wordp
## The Edit component's markup
-The [`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops) hook available on the [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor) allows passing the required attributes for the Block Editor to the `edit` block's outer wrapper.
+The [`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops) hook available on the [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor) allows passing the required attributes for the Block Editor to the `edit` block's outer wrapper.
Among other things, the `useBlockProps()` hook takes care of including in this wrapper:
-- An `id` for the block's markup
-- Some accesibility and `data-` attributes
+- An `id` for the block's markup
+- Some accessibility and `data-` attributes
- Classes and inline styles reflecting custom settings, which include by default:
- - The `wp-block` class
+ - The `wp-block` class
- A class that contains the name of the block with its namespace
For example, for the following piece of code of a block's registration in the client...
@@ -43,18 +43,18 @@ _(see the [code above](https://github.com/WordPress/block-development-examples/b
...the markup of the block in the Block Editor could look like this:
```html
-
Hello World - Block Editor
@@ -87,16 +87,16 @@ _(see the [code above](https://github.com/WordPress/block-development-examples/b
Hello World – Frontend
```
-Any additional classes and attributes for the `save` function of the block should be passed as an argument of `useBlockProps.save()` (see [example](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/stylesheets-79a4c3/src/save.js)).
+Any additional classes and attributes for the `save` function of the block should be passed as an argument of `useBlockProps.save()` (see [example](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/stylesheets-79a4c3/src/save.js)).
When you add `supports` for any feature, the proper classes get added to the object returned by the `useBlockProps.save()` hook.
```html
Hello World
```
@@ -105,10 +105,10 @@ _(check the [example](https://github.com/WordPress/block-development-examples/tr
## The server-side render markup
-Any markup in the server-side render definition for the block can use the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to generate the string of attributes required to reflect the block settings (see [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11)).
+Any markup in the server-side render definition for the block can use the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to generate the string of attributes required to reflect the block settings (see [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11)).
```php
>
-```
\ No newline at end of file
+```
diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md
index 9be4f3a993d203..d3628d991f872f 100644
--- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md
+++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md
@@ -208,13 +208,13 @@ export const withBookQueryControls = ( BlockEdit ) => ( props ) => {
// function to handle that.
return isMyBooksVariation( props ) ? (
<>
-
+
{ /** Our custom component */ }
>
) : (
-
+
);
};
diff --git a/docs/how-to-guides/themes/global-settings-and-styles.md b/docs/how-to-guides/themes/global-settings-and-styles.md
index 130b6271d13bdf..617c435b6d70c5 100644
--- a/docs/how-to-guides/themes/global-settings-and-styles.md
+++ b/docs/how-to-guides/themes/global-settings-and-styles.md
@@ -233,6 +233,7 @@ The settings section has the following structure:
},
"custom": {},
"dimensions": {
+ "aspectRatio": false,
"minHeight": false,
},
"layout": {
@@ -773,6 +774,7 @@ Each block declares which style properties it exposes via the [block supports me
"text": "value"
},
"dimensions": {
+ "aspectRatio": "value",
"minHeight": "value"
},
"filter": {
diff --git a/docs/how-to-guides/themes/theme-support.md b/docs/how-to-guides/themes/theme-support.md
index 88e69938737b7a..4a952c4de657d1 100644
--- a/docs/how-to-guides/themes/theme-support.md
+++ b/docs/how-to-guides/themes/theme-support.md
@@ -472,7 +472,7 @@ Use this setting to enable the following Global Styles settings:
- color: link
- spacing: blockGap, margin, padding
- typography: lineHeight
-- dimensions: minHeight
+- dimensions: aspectRatio, minHeight
- position: sticky
```php
diff --git a/docs/manifest.json b/docs/manifest.json
index 67b8fac99f7137..e8c2e0d9d2b0f0 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1673,6 +1673,12 @@
"markdown_source": "../packages/icons/README.md",
"parent": "packages"
},
+ {
+ "title": "@wordpress/interactivity-router",
+ "slug": "packages-interactivity-router",
+ "markdown_source": "../packages/interactivity-router/README.md",
+ "parent": "packages"
+ },
{
"title": "@wordpress/interactivity",
"slug": "packages-interactivity",
diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md
index 4a59c34813448f..f035e026ff2e11 100644
--- a/docs/reference-guides/block-api/block-supports.md
+++ b/docs/reference-guides/block-api/block-supports.md
@@ -442,6 +442,7 @@ This value signals that a block supports some of the CSS style properties relate
```js
supports: {
dimensions: {
+ aspectRatio: true // Enable aspect ratio control.
minHeight: true // Enable min height control.
}
}
@@ -449,12 +450,13 @@ supports: {
When a block declares support for a specific dimensions property, its attributes definition is extended to include the `style` attribute.
-- `style`: attribute of `object` type with no default assigned. This is added when `minHeight` support is declared. It stores the custom values set by the user, e.g.:
+- `style`: attribute of `object` type with no default assigned. This is added when `aspectRatio` or `minHeight` support is declared. It stores the custom values set by the user, e.g.:
```js
attributes: {
style: {
dimensions: {
+ aspectRatio: "16/9",
minHeight: "50vh"
}
}
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index bee68530eeb239..0cf9bb77f71f82 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -42,7 +42,7 @@ Create and save content to reuse across your site. Update the pattern, and the c
- **Name:** core/block
- **Category:** reusable
- **Supports:** ~~customClassName~~, ~~html~~, ~~inserter~~, ~~renaming~~
-- **Attributes:** ref
+- **Attributes:** overrides, ref
## Button
@@ -238,7 +238,7 @@ Add an image or video with a text overlay. ([Source](https://github.com/WordPres
- **Name:** core/cover
- **Category:** media
-- **Supports:** align, anchor, color (heading, text, ~~background~~, ~~enableContrastChecker~~), layout (~~allowJustification~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align, anchor, color (heading, text, ~~background~~, ~~enableContrastChecker~~), dimensions (aspectRatio), layout (~~allowJustification~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** allowedBlocks, alt, backgroundType, contentPosition, customGradient, customOverlayColor, dimRatio, focalPoint, gradient, hasParallax, id, isDark, isRepeated, isUserOverlayColor, minHeight, minHeightUnit, overlayColor, tagName, templateLock, url, useFeaturedImage
## Details
@@ -341,7 +341,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute
- **Name:** core/group
- **Category:** design
-- **Supports:** align (full, wide), anchor, ariaLabel, background (backgroundImage, backgroundSize), color (background, button, gradients, heading, link, text), dimensions (minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (full, wide), anchor, ariaLabel, background (backgroundImage, backgroundSize), color (background, button, gradients, heading, link, text), dimensions (aspectRatio, minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** allowedBlocks, tagName, templateLock
## Heading
diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md
index 7b0bd386daaf48..b03e905d166bc0 100644
--- a/docs/reference-guides/data/data-core-block-editor.md
+++ b/docs/reference-guides/data/data-core-block-editor.md
@@ -409,6 +409,19 @@ _Returns_
- `WPBlock[]`: Block objects.
+### getBlocksByName
+
+Returns all blocks that match a blockName. Results include nested blocks.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _blockName_ `?string`: Optional block name, if not specified, returns an empty array.
+
+_Returns_
+
+- `Array`: Array of clientIds of blocks with name equal to blockName.
+
### getBlockSelectionEnd
Returns the current block selection end. This value may be null, and it may represent either a singular block selection or multi-selection end. A selection is singular if its start and end match.
diff --git a/docs/reference-guides/data/data-core-notices.md b/docs/reference-guides/data/data-core-notices.md
index e11e6f226169f9..d36098429811dd 100644
--- a/docs/reference-guides/data/data-core-notices.md
+++ b/docs/reference-guides/data/data-core-notices.md
@@ -277,7 +277,7 @@ export const ExampleComponent = () => {
const notices = useSelect( ( select ) =>
select( noticesStore ).getNotices()
);
- const { removeNotices } = useDispatch( noticesStore );
+ const { removeAllNotices } = useDispatch( noticesStore );
return (
<>
diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md
index 4c7e3df7cec128..35a041052889c0 100644
--- a/docs/reference-guides/filters/block-filters.md
+++ b/docs/reference-guides/filters/block-filters.md
@@ -19,11 +19,11 @@ _Example_:
```php
{
return ( props ) => {
return (
<>
-
+
My custom control
@@ -352,14 +352,14 @@ On the server, you can filter the list of blocks shown in the inserter using the
post ) ) {
return array( 'core/paragraph', 'core/heading' );
}
return $allowed_block_types;
}
-add_filter( 'allowed_block_types_all', 'filter_allowed_block_types_when_post_provided', 10, 2 );
+add_filter( 'allowed_block_types_all', 'wpdocs_filter_allowed_block_types_when_post_provided', 10, 2 );
```
## Managing block categories
@@ -374,7 +374,7 @@ It is possible to filter the list of default block categories using the `block_c
post ) ) {
array_push(
$block_categories,
@@ -388,7 +388,7 @@ function filter_block_categories_when_post_provided( $block_categories, $editor_
return $block_categories;
}
-add_filter( 'block_categories_all', 'filter_block_categories_when_post_provided', 10, 2 );
+add_filter( 'block_categories_all', 'wpdocs_filter_block_categories_when_post_provided', 10, 2 );
```
### `wp.blocks.updateCategory`
diff --git a/docs/reference-guides/filters/editor-filters.md b/docs/reference-guides/filters/editor-filters.md
index 59f6d7ef8213f3..943e161a1df49d 100644
--- a/docs/reference-guides/filters/editor-filters.md
+++ b/docs/reference-guides/filters/editor-filters.md
@@ -84,14 +84,14 @@ _Example:_
post ) ) {
$editor_settings['maxUploadFileSize'] = 12345;
}
return $editor_settings;
}
-add_filter( 'block_editor_settings_all', 'filter_block_editor_settings_when_post_provided', 10, 2 );
+add_filter( 'block_editor_settings_all', 'wpdocs_filter_block_editor_settings_when_post_provided', 10, 2 );
```
#### `block_editor_rest_api_preload_paths`
@@ -104,14 +104,14 @@ _Example:_
post ) ) {
array_push( $preload_paths, array( '/wp/v2/blocks', 'OPTIONS' ) );
}
return $preload_paths;
}
-add_filter( 'block_editor_rest_api_preload_paths', 'filter_block_editor_rest_api_preload_paths_when_post_provided', 10, 2 );
+add_filter( 'block_editor_rest_api_preload_paths', 'wpdocs_filter_block_editor_rest_api_preload_paths_when_post_provided', 10, 2 );
```
### Available default editor settings
diff --git a/docs/reference-guides/filters/global-styles-filters.md b/docs/reference-guides/filters/global-styles-filters.md
index 7d3b6be95768b9..59bbbfcd5921dd 100644
--- a/docs/reference-guides/filters/global-styles-filters.md
+++ b/docs/reference-guides/filters/global-styles-filters.md
@@ -14,7 +14,7 @@ _Example:_
This is how to pass a new color palette for the theme and disable the text color UI:
```php
-function filter_theme_json_theme( $theme_json ){
+function wpdocs_filter_theme_json_theme( $theme_json ){
$new_data = array(
'version' => 2,
'settings' => array(
@@ -38,5 +38,5 @@ function filter_theme_json_theme( $theme_json ){
return $theme_json->update_with( $new_data );
}
-add_filter( 'wp_theme_json_data_theme', 'filter_theme_json_theme' );
+add_filter( 'wp_theme_json_data_theme', 'wpdocs_filter_theme_json_theme' );
```
diff --git a/docs/reference-guides/filters/parser-filters.md b/docs/reference-guides/filters/parser-filters.md
index 3acfb2489182db..7adc68cc0bf7e7 100644
--- a/docs/reference-guides/filters/parser-filters.md
+++ b/docs/reference-guides/filters/parser-filters.md
@@ -26,11 +26,11 @@ class EmptyParser {
}
}
-function my_plugin_select_empty_parser( $prev_parser_class ) {
+function wpdocs_select_empty_parser( $prev_parser_class ) {
return 'EmptyParser';
}
-add_filter( 'block_parser_class', 'my_plugin_select_empty_parser', 10, 1 );
+add_filter( 'block_parser_class', 'wpdocs_select_empty_parser', 10, 1 );
```
> **Note**: At the present time it's not possible to replace the client-side parser.
diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md
index ee88f779ace1ce..5565336ffedb7f 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -34,7 +34,7 @@ Setting that enables the following UI tools:
- background: backgroundImage
- border: color, radius, style, width
- color: link
-- dimensions: minHeight
+- dimensions: aspectRatio, minHeight
- position: sticky
- spacing: blockGap, margin, padding
- typography: lineHeight
@@ -116,6 +116,7 @@ Settings related to dimensions.
| Property | Type | Default | Props |
| --- | --- | --- |--- |
+| aspectRatio | boolean | false | |
| minHeight | boolean | false | |
---
@@ -237,6 +238,7 @@ Dimensions styles
| Property | Type | Props |
| --- | --- |--- |
+| aspectRatio | string, object | |
| minHeight | string, object | |
---
diff --git a/gutenberg.php b/gutenberg.php
index febd63e0be0308..d78f218fe7fea8 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -5,7 +5,7 @@
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
* Requires at least: 6.3
* Requires PHP: 7.0
- * Version: 17.5.0
+ * Version: 17.6.0-rc.1
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/lib/block-supports/dimensions.php b/lib/block-supports/dimensions.php
index 1ef43133c2cdfb..1980faba278175 100644
--- a/lib/block-supports/dimensions.php
+++ b/lib/block-supports/dimensions.php
@@ -74,6 +74,83 @@ function gutenberg_apply_dimensions_support( $block_type, $block_attributes ) {
return $attributes;
}
+/**
+ * Renders server-side dimensions styles to the block wrapper.
+ * This block support uses the `render_block` hook to ensure that
+ * it is also applied to non-server-rendered blocks.
+ *
+ * @param string $block_content Rendered block content.
+ * @param array $block Block object.
+ * @return string Filtered block content.
+ */
+function gutenberg_render_dimensions_support( $block_content, $block ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
+ $block_attributes = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
+ $has_aspect_ratio_support = block_has_support( $block_type, array( 'dimensions', 'aspectRatio' ), false );
+
+ if (
+ ! $has_aspect_ratio_support ||
+ wp_should_skip_block_supports_serialization( $block_type, 'dimensions', 'aspectRatio' )
+ ) {
+ return $block_content;
+ }
+
+ $dimensions_block_styles = array();
+ $dimensions_block_styles['aspectRatio'] = $block_attributes['style']['dimensions']['aspectRatio'] ?? null;
+
+ // To ensure the aspect ratio does not get overridden by `minHeight` unset any existing rule.
+ if (
+ isset( $dimensions_block_styles['aspectRatio'] )
+ ) {
+ $dimensions_block_styles['minHeight'] = 'unset';
+ } elseif (
+ isset( $block_attributes['style']['dimensions']['minHeight'] ) ||
+ isset( $block_attributes['minHeight'] )
+ ) {
+ $dimensions_block_styles['aspectRatio'] = 'unset';
+ }
+
+ $styles = gutenberg_style_engine_get_styles( array( 'dimensions' => $dimensions_block_styles ) );
+
+ if ( ! empty( $styles['css'] ) ) {
+ // Inject dimensions styles to the first element, presuming it's the wrapper, if it exists.
+ $tags = new WP_HTML_Tag_Processor( $block_content );
+
+ if ( $tags->next_tag() ) {
+ $existing_style = $tags->get_attribute( 'style' );
+ $updated_style = '';
+
+ if ( ! empty( $existing_style ) ) {
+ $updated_style = $existing_style;
+ if ( ! str_ends_with( $existing_style, ';' ) ) {
+ $updated_style .= ';';
+ }
+ }
+
+ $updated_style .= $styles['css'];
+ $tags->set_attribute( 'style', $updated_style );
+
+ if ( ! empty( $styles['classnames'] ) ) {
+ foreach ( explode( ' ', $styles['classnames'] ) as $class_name ) {
+ if (
+ str_contains( $class_name, 'aspect-ratio' ) &&
+ ! isset( $block_attributes['style']['dimensions']['aspectRatio'] )
+ ) {
+ continue;
+ }
+ $tags->add_class( $class_name );
+ }
+ }
+ }
+
+ return $tags->get_updated_html();
+ }
+
+ return $block_content;
+}
+
+add_filter( 'render_block', 'gutenberg_render_dimensions_support', 10, 2 );
+
// Register the block support.
WP_Block_Supports::get_instance()->register(
'dimensions',
diff --git a/lib/block-supports/pattern.php b/lib/block-supports/pattern.php
deleted file mode 100644
index 0c5868c1fea0cb..00000000000000
--- a/lib/block-supports/pattern.php
+++ /dev/null
@@ -1,46 +0,0 @@
- array( 'content' ),
- 'core/heading' => array( 'content' ),
- 'core/image' => array( 'url', 'title', 'alt' ),
- 'core/button' => array( 'url', 'text' ),
- );
- $pattern_support = array_key_exists( $block_type->name, $allowed_blocks );
-
- if ( $pattern_support ) {
- if ( ! $block_type->uses_context ) {
- $block_type->uses_context = array();
- }
-
- if ( ! in_array( 'pattern/overrides', $block_type->uses_context, true ) ) {
- $block_type->uses_context[] = 'pattern/overrides';
- }
- }
- }
-
- // Register the block support.
- WP_Block_Supports::get_instance()->register(
- 'pattern',
- array(
- 'register_attribute' => 'gutenberg_register_pattern_support',
- )
- );
-}
diff --git a/lib/block-supports/shadow.php b/lib/block-supports/shadow.php
index 4a28c98b79325d..87258930faf10e 100644
--- a/lib/block-supports/shadow.php
+++ b/lib/block-supports/shadow.php
@@ -53,9 +53,8 @@ function gutenberg_apply_shadow_support( $block_type, $block_attributes ) {
$shadow_block_styles = array();
- $preset_shadow = array_key_exists( 'shadow', $block_attributes ) ? "var:preset|shadow|{$block_attributes['shadow']}" : null;
- $custom_shadow = isset( $block_attributes['style']['shadow'] ) ? $block_attributes['style']['shadow'] : null;
- $shadow_block_styles['shadow'] = $preset_shadow ? $preset_shadow : $custom_shadow;
+ $custom_shadow = $block_attributes['style']['shadow'] ?? null;
+ $shadow_block_styles['shadow'] = $custom_shadow;
$attributes = array();
$styles = gutenberg_style_engine_get_styles( $shadow_block_styles );
diff --git a/lib/blocks.php b/lib/blocks.php
index d5283afeb7f999..e1d4622a0f23da 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -440,27 +440,6 @@ function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $si
add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 );
-/**
- * Registers the metadata block attribute for all block types.
- *
- * @param array $args Array of arguments for registering a block type.
- * @return array $args
- */
-function gutenberg_register_metadata_attribute( $args ) {
- // Setup attributes if needed.
- if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
- $args['attributes'] = array();
- }
-
- if ( ! array_key_exists( 'metadata', $args['attributes'] ) ) {
- $args['attributes']['metadata'] = array(
- 'type' => 'object',
- );
- }
-
- return $args;
-}
-add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' );
/**
* Strips all HTML from the content of footnotes, and sanitizes the ID.
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index a063ab34d9069c..af074c22ead450 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -211,6 +211,7 @@ class WP_Theme_JSON_Gutenberg {
* @var array
*/
const PROPERTIES_METADATA = array(
+ 'aspect-ratio' => array( 'dimensions', 'aspectRatio' ),
'background' => array( 'color', 'gradient' ),
'background-color' => array( 'color', 'background' ),
'border-radius' => array( 'border', 'radius' ),
@@ -381,7 +382,8 @@ class WP_Theme_JSON_Gutenberg {
),
'custom' => null,
'dimensions' => array(
- 'minHeight' => null,
+ 'aspectRatio' => null,
+ 'minHeight' => null,
),
'layout' => array(
'contentSize' => null,
@@ -486,7 +488,8 @@ class WP_Theme_JSON_Gutenberg {
'text' => null,
),
'dimensions' => array(
- 'minHeight' => null,
+ 'aspectRatio' => null,
+ 'minHeight' => null,
),
'filter' => array(
'duotone' => null,
@@ -661,6 +664,7 @@ public static function get_element_class_name( $element ) {
array( 'color', 'heading' ),
array( 'color', 'button' ),
array( 'color', 'caption' ),
+ array( 'dimensions', 'aspectRatio' ),
array( 'dimensions', 'minHeight' ),
// BEGIN EXPERIMENTAL.
// Allow `position.fixed` to be opted-in by default.
@@ -1010,7 +1014,7 @@ protected static function get_blocks_metadata() {
if ( $duotone_support ) {
$root_selector = wp_get_block_css_selector( $block_type );
- $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support );
+ $duotone_selector = static::scope_selector( $root_selector, $duotone_support );
}
}
@@ -1185,7 +1189,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets'
$setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector'];
}
if ( false !== $root_style_key ) {
- $setting_nodes[ $root_style_key ]['selector'] = $options['root_selector'];
+ $style_nodes[ $root_style_key ]['selector'] = $options['root_selector'];
}
}
@@ -2093,6 +2097,15 @@ protected static function compute_style_properties( $styles, $settings = array()
$value = gutenberg_get_typography_font_size_value( array( 'size' => $value ) );
}
+ if ( 'aspect-ratio' === $css_property ) {
+ // For aspect ratio to work, other dimensions rules must be unset.
+ // This ensures that a fixed height does not override the aspect ratio.
+ $declarations[] = array(
+ 'name' => 'min-height',
+ 'value' => 'unset',
+ );
+ }
+
$declarations[] = array(
'name' => $css_property,
'value' => $value,
diff --git a/lib/compat/wordpress-6.5/block-bindings/block-bindings.php b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php
new file mode 100644
index 00000000000000..f9b33946613934
--- /dev/null
+++ b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php
@@ -0,0 +1,163 @@
+register_source( $source_name, $source_properties );
+ }
+}
+
+/**
+ * Retrieves the list of registered block sources.
+ *
+ * @return array The list of registered block sources.
+ */
+if ( ! function_exists( 'wp_block_bindings_get_sources' ) ) {
+ function wp_block_bindings_get_sources() {
+ return wp_block_bindings()->get_sources();
+ }
+}
+
+/**
+ * Replaces the HTML content of a block based on the provided source value.
+ *
+ * @param string $block_content Block Content.
+ * @param string $block_name The name of the block to process.
+ * @param string $block_attr The attribute of the block we want to process.
+ * @param string $source_value The value used to replace the HTML.
+ * @return string The modified block content.
+ */
+function gutenberg_block_bindings_replace_html( $block_content, $block_name, $block_attr, $source_value ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
+ if ( null === $block_type ) {
+ return;
+ }
+
+ // Depending on the attribute source, the processing will be different.
+ switch ( $block_type->attributes[ $block_attr ]['source'] ) {
+ case 'html':
+ case 'rich-text':
+ $block_reader = new WP_HTML_Tag_Processor( $block_content );
+
+ // TODO: Support for CSS selectors whenever they are ready in the HTML API.
+ // In the meantime, support comma-separated selectors by exploding them into an array.
+ $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] );
+ // Add a bookmark to the first tag to be able to iterate over the selectors.
+ $block_reader->next_tag();
+ $block_reader->set_bookmark( 'iterate-selectors' );
+
+ // TODO: This shouldn't be needed when the `set_inner_html` function is ready.
+ // Store the parent tag and its attributes to be able to restore them later in the button.
+ // The button block has a wrapper while the paragraph and heading blocks don't.
+ if ( 'core/button' === $block_name ) {
+ $button_wrapper = $block_reader->get_tag();
+ $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
+ $button_wrapper_attrs = array();
+ foreach ( $button_wrapper_attribute_names as $name ) {
+ $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name );
+ }
+ }
+
+ foreach ( $selectors as $selector ) {
+ // If the parent tag, or any of its children, matches the selector, replace the HTML.
+ if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag(
+ array(
+ 'tag_name' => $selector,
+ )
+ ) ) {
+ $block_reader->release_bookmark( 'iterate-selectors' );
+
+ // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
+ // Until then, it is hardcoded for the paragraph, heading, and button blocks.
+ // Store the tag and its attributes to be able to restore them later.
+ $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
+ $selector_attrs = array();
+ foreach ( $selector_attribute_names as $name ) {
+ $selector_attrs[ $name ] = $block_reader->get_attribute( $name );
+ }
+ $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "$selector>";
+ $amended_content = new WP_HTML_Tag_Processor( $selector_markup );
+ $amended_content->next_tag();
+ foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
+ $amended_content->set_attribute( $attribute_key, $attribute_value );
+ }
+ if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
+ return $amended_content->get_updated_html();
+ }
+ if ( 'core/button' === $block_name ) {
+ $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}$button_wrapper>";
+ $amended_button = new WP_HTML_Tag_Processor( $button_markup );
+ $amended_button->next_tag();
+ foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
+ $amended_button->set_attribute( $attribute_key, $attribute_value );
+ }
+ return $amended_button->get_updated_html();
+ }
+ } else {
+ $block_reader->seek( 'iterate-selectors' );
+ }
+ }
+ $block_reader->release_bookmark( 'iterate-selectors' );
+ return $block_content;
+
+ case 'attribute':
+ $amended_content = new WP_HTML_Tag_Processor( $block_content );
+ if ( ! $amended_content->next_tag(
+ array(
+ // TODO: build the query from CSS selector.
+ 'tag_name' => $block_type->attributes[ $block_attr ]['selector'],
+ )
+ ) ) {
+ return $block_content;
+ }
+ $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) );
+ return $amended_content->get_updated_html();
+ break;
+
+ default:
+ return $block_content;
+ break;
+ }
+ return;
+}
diff --git a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php
new file mode 100644
index 00000000000000..68b51348010e3a
--- /dev/null
+++ b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php
@@ -0,0 +1,63 @@
+sources[ $source_name ] = $source_properties;
+ }
+
+ /**
+ * Retrieves the list of registered block sources.
+ *
+ * @return array The array of registered sources.
+ */
+ public function get_sources() {
+ return $this->sources;
+ }
+}
diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php
new file mode 100644
index 00000000000000..65ddb7278e7035
--- /dev/null
+++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php
@@ -0,0 +1,37 @@
+attributes, array( 'metadata', 'id' ), false ) ) {
+ return null;
+ }
+ $block_id = $block_instance->attributes['metadata']['id'];
+ $attribute_override = _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null );
+ if ( null === $attribute_override ) {
+ return null;
+ }
+ switch ( $attribute_override[0] ) {
+ case 0: // remove
+ /**
+ * TODO: This currently doesn't remove the attribute, but only set it to an empty string.
+ * It's a temporary solution until the block binding API supports different operations.
+ */
+ return '';
+ case 1: // replace
+ return $attribute_override[1];
+ default:
+ return null;
+ }
+ };
+ wp_block_bindings_register_source(
+ 'pattern_attributes',
+ array(
+ 'label' => __( 'Pattern Attributes' ),
+ 'apply' => $pattern_source_callback,
+ )
+ );
+}
diff --git a/lib/experimental/block-bindings/sources/post-meta.php b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php
similarity index 87%
rename from lib/experimental/block-bindings/sources/post-meta.php
rename to lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php
index 2d53dab6d321ac..e52b4f289ccdd3 100644
--- a/lib/experimental/block-bindings/sources/post-meta.php
+++ b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php
@@ -18,7 +18,9 @@
};
wp_block_bindings_register_source(
'post_meta',
- __( 'Post Meta', 'gutenberg' ),
- $post_meta_source_callback
+ array(
+ 'label' => __( 'Post Meta' ),
+ 'apply' => $post_meta_source_callback,
+ )
);
}
diff --git a/lib/compat/wordpress-6.5/block-patterns.php b/lib/compat/wordpress-6.5/block-patterns.php
index f43acda2a1035c..cce97cb19c6902 100644
--- a/lib/compat/wordpress-6.5/block-patterns.php
+++ b/lib/compat/wordpress-6.5/block-patterns.php
@@ -49,7 +49,7 @@ function gutenberg_register_taxonomy_patterns() {
'singular_name' => _x( 'Pattern Category', 'taxonomy singular name' ),
'add_new_item' => __( 'Add New Category' ),
'add_or_remove_items' => __( 'Add or remove pattern categories' ),
- 'back_to_items' => __( '← Go to pattern categories' ),
+ 'back_to_items' => __( '← Go to Pattern Categories' ),
'choose_from_most_used' => __( 'Choose from the most used pattern categories' ),
'edit_item' => __( 'Edit Pattern Category' ),
'item_link' => __( 'Pattern Category Link' ),
diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php
index b6890c14dc1f97..e1b91364fe22e5 100644
--- a/lib/compat/wordpress-6.5/blocks.php
+++ b/lib/compat/wordpress-6.5/blocks.php
@@ -22,3 +22,105 @@ function gutenberg_register_block_type_args_shim( $args ) {
if ( ! method_exists( 'WP_Block_Type', 'get_variations' ) ) {
add_filter( 'register_block_type_args', 'gutenberg_register_block_type_args_shim' );
}
+
+
+/**
+ * Registers the metadata block attribute for all block types.
+ *
+ * @param array $args Array of arguments for registering a block type.
+ * @return array $args
+ */
+function gutenberg_register_metadata_attribute( $args ) {
+ // Setup attributes if needed.
+ if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
+ $args['attributes'] = array();
+ }
+
+ if ( ! array_key_exists( 'metadata', $args['attributes'] ) ) {
+ $args['attributes']['metadata'] = array(
+ 'type' => 'object',
+ );
+ }
+
+ return $args;
+}
+add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' );
+
+
+if ( ! function_exists( 'gutenberg_process_block_bindings' ) ) {
+ /**
+ * Process the block bindings attribute.
+ *
+ * @param string $block_content Block Content.
+ * @param array $block Block attributes.
+ * @param WP_Block $block_instance The block instance.
+ */
+ function gutenberg_process_block_bindings( $block_content, $block, $block_instance ) {
+
+ // Allowed blocks that support block bindings.
+ // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes?
+ $allowed_blocks = array(
+ 'core/paragraph' => array( 'content' ),
+ 'core/heading' => array( 'content' ),
+ 'core/image' => array( 'url', 'title', 'alt' ),
+ 'core/button' => array( 'url', 'text', 'linkTarget' ),
+ );
+
+ // If the block doesn't have the bindings property or isn't one of the allowed block types, return.
+ if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) {
+ return $block_content;
+ }
+
+ /*
+ * Assuming the following format for the bindings property of the "metadata" attribute:
+ *
+ * "bindings": {
+ * "title": {
+ * "source": {
+ * "name": "post_meta",
+ * "attributes": { "value": "text_custom_field" }
+ * }
+ * },
+ * "url": {
+ * "source": {
+ * "name": "post_meta",
+ * "attributes": { "value": "text_custom_field" }
+ * }
+ * }
+ * }
+ */
+
+ $block_bindings_sources = wp_block_bindings_get_sources();
+ $modified_block_content = $block_content;
+ foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) {
+
+ // If the attribute is not in the list, process next attribute.
+ if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) {
+ continue;
+ }
+ // If no source is provided, or that source is not registered, process next attribute.
+ if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) {
+ continue;
+ }
+
+ $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply'];
+ // Get the value based on the source.
+ if ( ! isset( $binding_source['source']['attributes'] ) ) {
+ $source_args = array();
+ } else {
+ $source_args = $binding_source['source']['attributes'];
+ }
+ $source_value = $source_callback( $source_args, $block_instance, $binding_attribute );
+ // If the value is null, process next attribute.
+ if ( is_null( $source_value ) ) {
+ continue;
+ }
+
+ // Process the HTML based on the block and the attribute.
+ $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value );
+ }
+ return $modified_block_content;
+ }
+}
+
+add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 );
diff --git a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php
deleted file mode 100644
index 9c270f59fa220e..00000000000000
--- a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php
+++ /dev/null
@@ -1,664 +0,0 @@
-.
- *
- * @var array
- */
- private static $nav_blocks_wrapped_in_list_item = array(
- 'core/navigation-link',
- 'core/home-link',
- 'core/site-title',
- 'core/site-logo',
- 'core/navigation-submenu',
- );
-
- /**
- * Used to determine which blocks need an wrapper.
- *
- * @var array
- */
- private static $needs_list_item_wrapper = array(
- 'core/site-title',
- 'core/site-logo',
- );
-
- /**
- * Keeps track of all the navigation names that have been seen.
- *
- * @var array
- */
- private static $seen_menu_names = array();
-
- /**
- * Returns whether or not this is responsive navigation.
- *
- * @param array $attributes The block attributes.
- * @return bool Returns whether or not this is responsive navigation.
- */
- private static function is_responsive( $attributes ) {
- /**
- * This is for backwards compatibility after the `isResponsive` attribute was been removed.
- */
-
- $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
- return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
- }
-
- /**
- * Returns whether or not a navigation has a submenu.
- *
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- * @return bool Returns whether or not a navigation has a submenu.
- */
- private static function has_submenus( $inner_blocks ) {
- foreach ( $inner_blocks as $inner_block ) {
- $inner_block_content = $inner_block->render();
- $p = new WP_HTML_Tag_Processor( $inner_block_content );
- if ( $p->next_tag(
- array(
- 'name' => 'LI',
- 'class_name' => 'has-child',
- )
- ) ) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Determine whether to load the view script.
- *
- * @param array $attributes The block attributes.
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- * @return bool Returns whether or not to load the view script.
- */
- private static function should_load_view_script( $attributes, $inner_blocks ) {
- $has_submenus = static::has_submenus( $inner_blocks );
- $is_responsive_menu = static::is_responsive( $attributes );
- return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu;
- }
-
- /**
- * Returns whether or not a block needs a list item wrapper.
- *
- * @param WP_Block $block The block.
- * @return bool Returns whether or not a block needs a list item wrapper.
- */
- private static function does_block_need_a_list_item_wrapper( $block ) {
- return in_array( $block->name, static::$needs_list_item_wrapper, true );
- }
-
- /**
- * Returns the markup for a single inner block.
- *
- * @param WP_Block $inner_block The inner block.
- * @return string Returns the markup for a single inner block.
- */
- private static function get_markup_for_inner_block( $inner_block ) {
- $inner_block_content = $inner_block->render();
- if ( ! empty( $inner_block_content ) ) {
- if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) {
- return ' ' . $inner_block_content . ' ';
- }
-
- return $inner_block_content;
- }
- }
-
- /**
- * Returns the html for the inner blocks of the navigation block.
- *
- * @param array $attributes The block attributes.
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- * @return string Returns the html for the inner blocks of the navigation block.
- */
- private static function get_inner_blocks_html( $attributes, $inner_blocks ) {
- $has_submenus = static::has_submenus( $inner_blocks );
- $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks );
-
- $style = static::get_styles( $attributes );
- $class = static::get_classes( $attributes );
- $container_attributes = get_block_wrapper_attributes(
- array(
- 'class' => 'wp-block-navigation__container ' . $class,
- 'style' => $style,
- )
- );
-
- $inner_blocks_html = '';
- $is_list_open = false;
-
- foreach ( $inner_blocks as $inner_block ) {
- $is_list_item = in_array( $inner_block->name, static::$nav_blocks_wrapped_in_list_item, true );
-
- if ( $is_list_item && ! $is_list_open ) {
- $is_list_open = true;
- $inner_blocks_html .= sprintf(
- '',
- $container_attributes
- );
- }
-
- if ( ! $is_list_item && $is_list_open ) {
- $is_list_open = false;
- $inner_blocks_html .= ' ';
- }
-
- $inner_blocks_html .= static::get_markup_for_inner_block( $inner_block );
- }
-
- if ( $is_list_open ) {
- $inner_blocks_html .= ' ';
- }
-
- // Add directives to the submenu if needed.
- if ( $has_submenus && $should_load_view_script ) {
- $tags = new WP_HTML_Tag_Processor( $inner_blocks_html );
- $inner_blocks_html = gutenberg_block_core_navigation_add_directives_to_submenu( $tags, $attributes );
- }
-
- return $inner_blocks_html;
- }
-
- /**
- * Gets the inner blocks for the navigation block from the navigation post.
- *
- * @param array $attributes The block attributes.
- * @return WP_Block_List Returns the inner blocks for the navigation block.
- */
- private static function get_inner_blocks_from_navigation_post( $attributes ) {
- $navigation_post = get_post( $attributes['ref'] );
- if ( ! isset( $navigation_post ) ) {
- return new WP_Block_List( array(), $attributes );
- }
-
- // Only published posts are valid. If this is changed then a corresponding change
- // must also be implemented in `use-navigation-menu.js`.
- if ( 'publish' === $navigation_post->post_status ) {
- $parsed_blocks = parse_blocks( $navigation_post->post_content );
-
- // 'parse_blocks' includes a null block with '\n\n' as the content when
- // it encounters whitespace. This code strips it.
- $compacted_blocks = gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks );
-
- // TODO - this uses the full navigation block attributes for the
- // context which could be refined.
- return new WP_Block_List( $compacted_blocks, $attributes );
- }
- }
-
- /**
- * Gets the inner blocks for the navigation block from the fallback.
- *
- * @param array $attributes The block attributes.
- * @return WP_Block_List Returns the inner blocks for the navigation block.
- */
- private static function get_inner_blocks_from_fallback( $attributes ) {
- $fallback_blocks = gutenberg_block_core_navigation_get_fallback_blocks();
-
- // Fallback my have been filtered so do basic test for validity.
- if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) {
- return new WP_Block_List( array(), $attributes );
- }
-
- return new WP_Block_List( $fallback_blocks, $attributes );
- }
-
- /**
- * Gets the inner blocks for the navigation block.
- *
- * @param array $attributes The block attributes.
- * @param WP_Block $block The parsed block.
- * @return WP_Block_List Returns the inner blocks for the navigation block.
- */
- private static function get_inner_blocks( $attributes, $block ) {
- $inner_blocks = $block->inner_blocks;
-
- // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render.
- if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
- $attributes['ref'] = $attributes['navigationMenuId'];
- }
-
- // If:
- // - the gutenberg plugin is active
- // - `__unstableLocation` is defined
- // - we have menu items at the defined location
- // - we don't have a relationship to a `wp_navigation` Post (via `ref`).
- // ...then create inner blocks from the classic menu assigned to that location.
- if (
- defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN &&
- array_key_exists( '__unstableLocation', $attributes ) &&
- ! array_key_exists( 'ref', $attributes ) &&
- ! empty( gutenberg_block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) )
- ) {
- $inner_blocks = gutenberg_block_core_navigation_get_inner_blocks_from_unstable_location( $attributes );
- }
-
- // Load inner blocks from the navigation post.
- if ( array_key_exists( 'ref', $attributes ) ) {
- $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes );
- }
-
- // If there are no inner blocks then fallback to rendering an appropriate fallback.
- if ( empty( $inner_blocks ) ) {
- $inner_blocks = static::get_inner_blocks_from_fallback( $attributes );
- }
-
- /**
- * Filter navigation block $inner_blocks.
- * Allows modification of a navigation block menu items.
- *
- * @since 6.1.0
- *
- * @param \WP_Block_List $inner_blocks
- */
- $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks );
-
- $post_ids = gutenberg_block_core_navigation_get_post_ids( $inner_blocks );
- if ( $post_ids ) {
- _prime_post_caches( $post_ids, false, false );
- }
-
- return $inner_blocks;
- }
-
- /**
- * Gets the name of the current navigation, if it has one.
- *
- * @param array $attributes The block attributes.
- * @return string Returns the name of the navigation.
- */
- private static function get_navigation_name( $attributes ) {
-
- $navigation_name = $attributes['ariaLabel'] ?? '';
-
- // Load the navigation post.
- if ( array_key_exists( 'ref', $attributes ) ) {
- $navigation_post = get_post( $attributes['ref'] );
- if ( ! isset( $navigation_post ) ) {
- return $navigation_name;
- }
-
- // Only published posts are valid. If this is changed then a corresponding change
- // must also be implemented in `use-navigation-menu.js`.
- if ( 'publish' === $navigation_post->post_status ) {
- $navigation_name = $navigation_post->post_title;
-
- // This is used to count the number of times a navigation name has been seen,
- // so that we can ensure every navigation has a unique id.
- if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) {
- ++static::$seen_menu_names[ $navigation_name ];
- } else {
- static::$seen_menu_names[ $navigation_name ] = 1;
- }
- }
- }
-
- return $navigation_name;
- }
-
- /**
- * Returns the layout class for the navigation block.
- *
- * @param array $attributes The block attributes.
- * @return string Returns the layout class for the navigation block.
- */
- private static function get_layout_class( $attributes ) {
- $layout_justification = array(
- 'left' => 'items-justified-left',
- 'right' => 'items-justified-right',
- 'center' => 'items-justified-center',
- 'space-between' => 'items-justified-space-between',
- );
-
- $layout_class = '';
- if (
- isset( $attributes['layout']['justifyContent'] ) &&
- isset( $layout_justification[ $attributes['layout']['justifyContent'] ] )
- ) {
- $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ];
- }
- if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) {
- $layout_class .= ' is-vertical';
- }
-
- if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) {
- $layout_class .= ' no-wrap';
- }
- return $layout_class;
- }
-
- /**
- * Return classes for the navigation block.
- *
- * @param array $attributes The block attributes.
- * @return string Returns the classes for the navigation block.
- */
- private static function get_classes( $attributes ) {
- // Restore legacy classnames for submenu positioning.
- $layout_class = static::get_layout_class( $attributes );
- $colors = gutenberg_block_core_navigation_build_css_colors( $attributes );
- $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes );
- $is_responsive_menu = static::is_responsive( $attributes );
-
- // Manually add block support text decoration as CSS class.
- $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null;
- $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration );
-
- // Sets the is-collapsed class when the navigation is set to always use the overlay.
- // This saves us from needing to do this check in the view.js file (see the collapseNav function).
- $is_collapsed_class = static::is_always_overlay( $attributes ) ? array( 'is-collapsed' ) : array();
-
- $classes = array_merge(
- $colors['css_classes'],
- $font_sizes['css_classes'],
- $is_responsive_menu ? array( 'is-responsive' ) : array(),
- $layout_class ? array( $layout_class ) : array(),
- $text_decoration ? array( $text_decoration_class ) : array(),
- $is_collapsed_class
- );
- return implode( ' ', $classes );
- }
-
- private static function is_always_overlay( $attributes ) {
- return isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
- }
-
- /**
- * Get styles for the navigation block.
- *
- * @param array $attributes The block attributes.
- * @return string Returns the styles for the navigation block.
- */
- private static function get_styles( $attributes ) {
- $colors = gutenberg_block_core_navigation_build_css_colors( $attributes );
- $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes );
- $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
- return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'];
- }
-
- /**
- * Get the responsive container markup
- *
- * @param array $attributes The block attributes.
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- * @param string $inner_blocks_html The markup for the inner blocks.
- * @return string Returns the container markup.
- */
- private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) {
- $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks );
- $colors = gutenberg_block_core_navigation_build_css_colors( $attributes );
- $modal_unique_id = wp_unique_id( 'modal-' );
-
- $responsive_container_classes = array(
- 'wp-block-navigation__responsive-container',
- implode( ' ', $colors['overlay_css_classes'] ),
- );
- $open_button_classes = array(
- 'wp-block-navigation__responsive-container-open',
- );
-
- $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon'];
- $toggle_button_icon = '
';
- if ( isset( $attributes['icon'] ) ) {
- if ( 'menu' === $attributes['icon'] ) {
- $toggle_button_icon = '
';
- }
- }
- $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' );
- $toggle_close_button_icon = '
';
- $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' );
- $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label.
- $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label.
-
- // Add Interactivity API directives to the markup if needed.
- $open_button_directives = '';
- $responsive_container_directives = '';
- $responsive_dialog_directives = '';
- $close_button_directives = '';
- if ( $should_load_view_script ) {
- $open_button_directives = '
- data-wp-on--click="actions.openMenuOnClick"
- data-wp-on--keydown="actions.handleMenuKeydown"
- ';
- $responsive_container_directives = '
- data-wp-class--has-modal-open="state.isMenuOpen"
- data-wp-class--is-menu-open="state.isMenuOpen"
- data-wp-watch="callbacks.initMenu"
- data-wp-on--keydown="actions.handleMenuKeydown"
- data-wp-on--focusout="actions.handleMenuFocusout"
- tabindex="-1"
- ';
- $responsive_dialog_directives = '
- data-wp-bind--aria-modal="state.ariaModal"
- data-wp-bind--aria-label="state.ariaLabel"
- data-wp-bind--role="state.roleAttribute"
- ';
- $close_button_directives = '
- data-wp-on--click="actions.closeMenuOnClick"
- ';
- $responsive_container_content_directives = '
- data-wp-watch="callbacks.focusFirstElement"
- ';
- }
-
- return sprintf(
- '
%8$s
-
',
- esc_attr( $modal_unique_id ),
- $inner_blocks_html,
- $toggle_aria_label_open,
- $toggle_aria_label_close,
- esc_attr( implode( ' ', $responsive_container_classes ) ),
- esc_attr( implode( ' ', $open_button_classes ) ),
- esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ),
- $toggle_button_content,
- $toggle_close_button_content,
- $open_button_directives,
- $responsive_container_directives,
- $responsive_dialog_directives,
- $close_button_directives,
- $responsive_container_content_directives
- );
- }
-
- /**
- * Get the wrapper attributes
- *
- * @param array $attributes The block attributes.
- * @param WP_Block_List $inner_blocks A list of inner blocks.
- * @return string Returns the navigation block markup.
- */
- private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) {
- $nav_menu_name = static::get_unique_navigation_name( $attributes );
- $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks );
- $is_responsive_menu = static::is_responsive( $attributes );
- $style = static::get_styles( $attributes );
- $class = static::get_classes( $attributes );
- $wrapper_attributes = get_block_wrapper_attributes(
- array(
- 'class' => $class,
- 'style' => $style,
- 'aria-label' => $nav_menu_name,
- )
- );
-
- if ( $is_responsive_menu ) {
- $nav_element_directives = static::get_nav_element_directives( $should_load_view_script, $attributes );
- $wrapper_attributes .= ' ' . $nav_element_directives;
- }
-
- return $wrapper_attributes;
- }
-
- /**
- * Get the nav element directives
- *
- * @param bool $should_load_view_script Whether or not the view script should be loaded.
- * @return string the directives for the navigation element.
- */
- private static function get_nav_element_directives( $should_load_view_script, $attributes ) {
- if ( ! $should_load_view_script ) {
- return '';
- }
- // When adding to this array be mindful of security concerns.
- $nav_element_context = wp_json_encode(
- array(
- 'overlayOpenedBy' => array(),
- 'type' => 'overlay',
- 'roleAttribute' => '',
- 'ariaLabel' => __( 'Menu' ),
- ),
- JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP
- );
- $nav_element_directives = '
- data-wp-interactive=\'{"namespace":"core/navigation"}\'
- data-wp-context=\'' . $nav_element_context . '\'
- ';
-
- // When the navigation overlayMenu attribute is set to "always"
- // we don't need to use JavaScript to collapse the menu as we set the class manually.
- if ( ! static::is_always_overlay( $attributes ) ) {
- $nav_element_directives .= 'data-wp-init="callbacks.initNav"';
- $nav_element_directives .= ' '; // space separator
- $nav_element_directives .= 'data-wp-class--is-collapsed="context.isCollapsed"';
- }
-
- return $nav_element_directives;
- }
-
- /**
- * Handle view script loading.
- *
- * @param array $attributes The block attributes.
- * @param WP_Block $block The parsed block.
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- */
- private static function handle_view_script_loading( $attributes, $block, $inner_blocks ) {
- $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks );
- $is_gutenberg_plugin = defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN;
- $view_js_file = 'wp-block-navigation-view';
- $script_handles = $block->block_type->view_script_handles;
-
- if ( $is_gutenberg_plugin ) {
- if ( $should_load_view_script ) {
- gutenberg_enqueue_module( '@wordpress/block-library/navigation-block' );
- }
- // Remove the view script because we are using the module.
- $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) );
- } else {
- // If the script already exists, there is no point in removing it from viewScript.
- if ( ! wp_script_is( $view_js_file ) ) {
-
- // If the script is not needed, and it is still in the `view_script_handles`, remove it.
- if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) {
- $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) );
- }
- // If the script is needed, but it was previously removed, add it again.
- if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) {
- $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) );
- }
- }
- }
- }
-
- /**
- * Returns the markup for the navigation block.
- *
- * @param array $attributes The block attributes.
- * @param WP_Block_List $inner_blocks The list of inner blocks.
- * @return string Returns the navigation wrapper markup.
- */
- private static function get_wrapper_markup( $attributes, $inner_blocks ) {
- $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks );
- if ( static::is_responsive( $attributes ) ) {
- return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html );
- }
- return $inner_blocks_html;
- }
-
- /**
- * Returns a unique name for the navigation.
- *
- * @param array $attributes The block attributes.
- * @return string Returns a unique name for the navigation.
- */
- private static function get_unique_navigation_name( $attributes ) {
- $nav_menu_name = static::get_navigation_name( $attributes );
-
- // If the menu name has been used previously then append an ID
- // to the name to ensure uniqueness across a given post.
- if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) {
- $count = static::$seen_menu_names[ $nav_menu_name ];
- $nav_menu_name = $nav_menu_name . ' ' . ( $count );
- }
-
- return $nav_menu_name;
- }
-
- /**
- * Renders the navigation block.
- *
- * @param array $attributes The block attributes.
- * @param string $content The saved content.
- * @param WP_Block $block The parsed block.
- * @return string Returns the navigation block markup.
- */
- public static function render( $attributes, $content, $block ) {
- /**
- * Deprecated:
- * The rgbTextColor and rgbBackgroundColor attributes
- * have been deprecated in favor of
- * customTextColor and customBackgroundColor ones.
- * Move the values from old attrs to the new ones.
- */
- if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
- $attributes['customTextColor'] = $attributes['rgbTextColor'];
- }
-
- if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
- $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
- }
-
- unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
-
- $inner_blocks = static::get_inner_blocks( $attributes, $block );
- // Prevent navigation blocks referencing themselves from rendering.
- if ( gutenberg_block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) {
- return '';
- }
-
- static::handle_view_script_loading( $attributes, $block, $inner_blocks );
-
- return sprintf(
- '
%2$s ',
- static::get_nav_wrapper_attributes( $attributes, $inner_blocks ),
- static::get_wrapper_markup( $attributes, $inner_blocks )
- );
- }
-}
diff --git a/lib/compat/wordpress-6.5/class-wp-script-modules.php b/lib/compat/wordpress-6.5/class-wp-script-modules.php
new file mode 100644
index 00000000000000..f6a2a348f92ef3
--- /dev/null
+++ b/lib/compat/wordpress-6.5/class-wp-script-modules.php
@@ -0,0 +1,323 @@
+
+ */
+ private $enqueued_before_registered = array();
+
+ /**
+ * Registers the script module if no script module with that script module
+ * identifier has already been registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module. Should be unique. It will be used in the
+ * final import map.
+ * @param string $src Optional. Full URL of the script module, or path of the script module relative
+ * to the WordPress root directory. If it is provided and the script module has
+ * not been registered yet, it will be registered.
+ * @param array $deps {
+ * Optional. List of dependencies.
+ *
+ * @type string|array $0... {
+ * An array of script module identifiers of the dependencies of this script
+ * module. The dependencies can be strings or arrays. If they are arrays,
+ * they need an `id` key with the script module identifier, and can contain
+ * an `import` key with either `static` or `dynamic`. By default,
+ * dependencies that don't contain an `import` key are considered static.
+ *
+ * @type string $id The script module identifier.
+ * @type string $import Optional. Import type. May be either `static` or
+ * `dynamic`. Defaults to `static`.
+ * }
+ * }
+ * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false.
+ * It is added to the URL as a query string for cache busting purposes. If $version
+ * is set to false, the version number is the currently installed WordPress version.
+ * If $version is set to null, no version is added.
+ */
+ public function register( string $id, string $src, array $deps = array(), $version = false ) {
+ if ( ! isset( $this->registered[ $id ] ) ) {
+ $dependencies = array();
+ foreach ( $deps as $dependency ) {
+ if ( is_array( $dependency ) ) {
+ if ( ! isset( $dependency['id'] ) ) {
+ _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
+ continue;
+ }
+ $dependencies[] = array(
+ 'id' => $dependency['id'],
+ 'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static',
+ );
+ } elseif ( is_string( $dependency ) ) {
+ $dependencies[] = array(
+ 'id' => $dependency,
+ 'import' => 'static',
+ );
+ } else {
+ _doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' );
+ }
+ }
+
+ $this->registered[ $id ] = array(
+ 'src' => $src,
+ 'version' => $version,
+ 'enqueue' => isset( $this->enqueued_before_registered[ $id ] ),
+ 'dependencies' => $dependencies,
+ );
+ }
+ }
+
+ /**
+ * Marks the script module to be enqueued in the page.
+ *
+ * If a src is provided and the script module has not been registered yet, it
+ * will be registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module. Should be unique. It will be used in the
+ * final import map.
+ * @param string $src Optional. Full URL of the script module, or path of the script module relative
+ * to the WordPress root directory. If it is provided and the script module has
+ * not been registered yet, it will be registered.
+ * @param array $deps {
+ * Optional. List of dependencies.
+ *
+ * @type string|array $0... {
+ * An array of script module identifiers of the dependencies of this script
+ * module. The dependencies can be strings or arrays. If they are arrays,
+ * they need an `id` key with the script module identifier, and can contain
+ * an `import` key with either `static` or `dynamic`. By default,
+ * dependencies that don't contain an `import` key are considered static.
+ *
+ * @type string $id The script module identifier.
+ * @type string $import Optional. Import type. May be either `static` or
+ * `dynamic`. Defaults to `static`.
+ * }
+ * }
+ * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false.
+ * It is added to the URL as a query string for cache busting purposes. If $version
+ * is set to false, the version number is the currently installed WordPress version.
+ * If $version is set to null, no version is added.
+ */
+ public function enqueue( string $id, string $src = '', array $deps = array(), $version = false ) {
+ if ( isset( $this->registered[ $id ] ) ) {
+ $this->registered[ $id ]['enqueue'] = true;
+ } elseif ( $src ) {
+ $this->register( $id, $src, $deps, $version );
+ $this->registered[ $id ]['enqueue'] = true;
+ } else {
+ $this->enqueued_before_registered[ $id ] = true;
+ }
+ }
+
+ /**
+ * Unmarks the script module so it will no longer be enqueued in the page.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module.
+ */
+ public function dequeue( string $id ) {
+ if ( isset( $this->registered[ $id ] ) ) {
+ $this->registered[ $id ]['enqueue'] = false;
+ }
+ unset( $this->enqueued_before_registered[ $id ] );
+ }
+
+ /**
+ * Adds the hooks to print the import map, enqueued script modules and script
+ * module preloads.
+ *
+ * In classic themes, the script modules used by the blocks are not yet known
+ * when the `wp_head` actions is fired, so it needs to print everything in the
+ * footer.
+ *
+ * @since 6.5.0
+ */
+ public function add_hooks() {
+ $position = wp_is_block_theme() ? 'wp_head' : 'wp_footer';
+ add_action( $position, array( $this, 'print_import_map' ) );
+ add_action( $position, array( $this, 'print_enqueued_script_modules' ) );
+ add_action( $position, array( $this, 'print_script_module_preloads' ) );
+ }
+
+ /**
+ * Prints the enqueued script modules using script tags with type="module"
+ * attributes.
+ *
+ * @since 6.5.0
+ */
+ public function print_enqueued_script_modules() {
+ foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) {
+ wp_print_script_tag(
+ array(
+ 'type' => 'module',
+ 'src' => $this->get_versioned_src( $script_module ),
+ 'id' => $id . '-js-module',
+ )
+ );
+ }
+ }
+
+ /**
+ * Prints the the static dependencies of the enqueued script modules using
+ * link tags with rel="modulepreload" attributes.
+ *
+ * If a script module is marked for enqueue, it will not be preloaded.
+ *
+ * @since 6.5.0
+ */
+ public function print_script_module_preloads() {
+ foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $id => $script_module ) {
+ // Don't preload if it's marked for enqueue.
+ if ( true !== $script_module['enqueue'] ) {
+ echo sprintf(
+ '
',
+ esc_url( $this->get_versioned_src( $script_module ) ),
+ esc_attr( $id . '-js-modulepreload' )
+ );
+ }
+ }
+ }
+
+ /**
+ * Prints the import map using a script tag with a type="importmap" attribute.
+ *
+ * @since 6.5.0
+ */
+ public function print_import_map() {
+ $import_map = $this->get_import_map();
+ if ( ! empty( $import_map['imports'] ) ) {
+ wp_print_inline_script_tag(
+ wp_json_encode( $import_map, JSON_HEX_TAG | JSON_HEX_AMP ),
+ array(
+ 'type' => 'importmap',
+ 'id' => 'wp-importmap',
+ )
+ );
+ }
+ }
+
+ /**
+ * Returns the import map array.
+ *
+ * @since 6.5.0
+ *
+ * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective
+ * URLs, including the version query.
+ */
+ private function get_import_map(): array {
+ $imports = array();
+ foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $id => $script_module ) {
+ $imports[ $id ] = $this->get_versioned_src( $script_module );
+ }
+ return array( 'imports' => $imports );
+ }
+
+ /**
+ * Retrieves the list of script modules marked for enqueue.
+ *
+ * @since 6.5.0
+ *
+ * @return array Script modules marked for enqueue, keyed by script module identifier.
+ */
+ private function get_marked_for_enqueue(): array {
+ $enqueued = array();
+ foreach ( $this->registered as $id => $script_module ) {
+ if ( true === $script_module['enqueue'] ) {
+ $enqueued[ $id ] = $script_module;
+ }
+ }
+ return $enqueued;
+ }
+
+ /**
+ * Retrieves all the dependencies for the given script module identifiers,
+ * filtered by import types.
+ *
+ * It will consolidate an array containing a set of unique dependencies based
+ * on the requested import types: 'static', 'dynamic', or both. This method is
+ * recursive and also retrieves dependencies of the dependencies.
+ *
+ * @since 6.5.0
+ *
+
+ * @param string[] $ids The identifiers of the script modules for which to gather dependencies.
+ * @param array $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
+ * Default is both.
+ * @return array List of dependencies, keyed by script module identifier.
+ */
+ private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ) {
+ return array_reduce(
+ $ids,
+ function ( $dependency_script_modules, $id ) use ( $import_types ) {
+ $dependencies = array();
+ foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
+ if (
+ in_array( $dependency['import'], $import_types, true ) &&
+ isset( $this->registered[ $dependency['id'] ] ) &&
+ ! isset( $dependency_script_modules[ $dependency['id'] ] )
+ ) {
+ $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ];
+ }
+ }
+ return array_merge( $dependency_script_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) );
+ },
+ array()
+ );
+ }
+
+ /**
+ * Gets the versioned URL for a script module src.
+ *
+ * If $version is set to false, the version number is the currently installed
+ * WordPress version. If $version is set to null, no version is added.
+ * Otherwise, the string passed in $version is used.
+ *
+ * @since 6.5.0
+ *
+ * @param array $script_module The script module.
+ * @return string The script module src with a version if relevant.
+ */
+ private function get_versioned_src( array $script_module ): string {
+ $args = array();
+ if ( false === $script_module['version'] ) {
+ $args['ver'] = get_bloginfo( 'version' );
+ } elseif ( null !== $script_module['version'] ) {
+ $args['ver'] = $script_module['version'];
+ }
+ if ( $args ) {
+ return add_query_arg( $args, $script_module['src'] );
+ }
+ return $script_module['src'];
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api-directives-processor.php b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api-directives-processor.php
new file mode 100644
index 00000000000000..b437bcefa67568
--- /dev/null
+++ b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api-directives-processor.php
@@ -0,0 +1,143 @@
+get_balanced_tag_bookmarks();
+ if ( ! $bookmarks ) {
+ return null;
+ }
+ list( $start_name, $end_name ) = $bookmarks;
+
+ $start = $this->bookmarks[ $start_name ]->start + $this->bookmarks[ $start_name ]->length + 1;
+ $end = $this->bookmarks[ $end_name ]->start;
+
+ $this->seek( $start_name );
+ $this->release_bookmark( $start_name );
+ $this->release_bookmark( $end_name );
+
+ return substr( $this->html, $start, $end - $start );
+ }
+
+ /**
+ * Sets the content between two balanced tags.
+ *
+ * @access private
+ *
+ * @param string $new_content The string to replace the content between the matching tags.
+ * @return bool Whether the content was successfully replaced.
+ */
+ public function set_content_between_balanced_tags( string $new_content ): bool {
+ $this->get_updated_html();
+
+ $bookmarks = $this->get_balanced_tag_bookmarks();
+ if ( ! $bookmarks ) {
+ return false;
+ }
+ list( $start_name, $end_name ) = $bookmarks;
+
+ $start = $this->bookmarks[ $start_name ]->start + $this->bookmarks[ $start_name ]->length + 1;
+ $end = $this->bookmarks[ $end_name ]->start;
+
+ $this->seek( $start_name );
+ $this->release_bookmark( $start_name );
+ $this->release_bookmark( $end_name );
+
+ $this->lexical_updates[] = new Gutenberg_HTML_Text_Replacement_6_5( $start, $end - $start, esc_html( $new_content ) );
+ return true;
+ }
+
+ /**
+ * Returns a pair of bookmarks for the current opening tag and the matching
+ * closing tag.
+ *
+ * @return array|null A pair of bookmarks, or null if there's no matching closing tag.
+ */
+ private function get_balanced_tag_bookmarks() {
+ static $i = 0;
+ $start_name = 'start_of_balanced_tag_' . ++$i;
+
+ $this->set_bookmark( $start_name );
+ if ( ! $this->next_balanced_closer() ) {
+ $this->release_bookmark( $start_name );
+ return null;
+ }
+
+ $end_name = 'end_of_balanced_tag_' . ++$i;
+ $this->set_bookmark( $end_name );
+
+ return array( $start_name, $end_name );
+ }
+
+ /**
+ * Finds the matching closing tag for an opening tag.
+ *
+ * When called while the processor is on an open tag, it traverses the HTML
+ * until it finds the matching closing tag, respecting any in-between content,
+ * including nested tags of the same name. Returns false when called on a
+ * closing or void tag, or if no matching closing tag was found.
+ *
+ * @return bool Whether a matching closing tag was found.
+ */
+ private function next_balanced_closer(): bool {
+ $depth = 0;
+ $tag_name = $this->get_tag();
+
+ if ( $this->is_void() ) {
+ return false;
+ }
+
+ while ( $this->next_tag(
+ array(
+ 'tag_name' => $tag_name,
+ 'tag_closers' => 'visit',
+ )
+ ) ) {
+ if ( ! $this->is_tag_closer() ) {
+ ++$depth;
+ continue;
+ }
+
+ if ( 0 === $depth ) {
+ return true;
+ }
+
+ --$depth;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the current tag is void.
+ *
+ * @access private
+ *
+ * @return bool Whether the current tag is void or not.
+ */
+ public function is_void(): bool {
+ $tag_name = $this->get_tag();
+ return Gutenberg_HTML_Processor_6_5::is_void( null !== $tag_name ? $tag_name : '' );
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php
new file mode 100644
index 00000000000000..ad9e5d7c439533
--- /dev/null
+++ b/lib/compat/wordpress-6.5/interactivity-api/class-wp-interactivity-api.php
@@ -0,0 +1,678 @@
+ 'data_wp_interactive_processor',
+ 'data-wp-context' => 'data_wp_context_processor',
+ 'data-wp-bind' => 'data_wp_bind_processor',
+ 'data-wp-class' => 'data_wp_class_processor',
+ 'data-wp-style' => 'data_wp_style_processor',
+ 'data-wp-text' => 'data_wp_text_processor',
+ );
+
+ /**
+ * Holds the initial state of the different Interactivity API stores.
+ *
+ * This state is used during the server directive processing. Then, it is
+ * serialized and sent to the client as part of the interactivity data to be
+ * recovered during the hydration of the client interactivity stores.
+ *
+ * @since 6.5.0
+ * @var array
+ */
+ private $state_data = array();
+
+ /**
+ * Holds the configuration required by the different Interactivity API stores.
+ *
+ * This configuration is serialized and sent to the client as part of the
+ * interactivity data and can be accessed by the client interactivity stores.
+ *
+ * @since 6.5.0
+ * @var array
+ */
+ private $config_data = array();
+
+ /**
+ * Gets and/or sets the initial state of an Interactivity API store for a
+ * given namespace.
+ *
+ * If state for that store namespace already exists, it merges the new
+ * provided state with the existing one.
+ *
+ * @since 6.5.0
+ *
+ * @param string $store_namespace The unique store namespace identifier.
+ * @param array $state Optional. The array that will be merged with the existing state for the specified
+ * store namespace.
+ * @return array The current state for the specified store namespace.
+ */
+ public function state( string $store_namespace, array $state = null ): array {
+ if ( ! isset( $this->state_data[ $store_namespace ] ) ) {
+ $this->state_data[ $store_namespace ] = array();
+ }
+ if ( is_array( $state ) ) {
+ $this->state_data[ $store_namespace ] = array_replace_recursive(
+ $this->state_data[ $store_namespace ],
+ $state
+ );
+ }
+ return $this->state_data[ $store_namespace ];
+ }
+
+ /**
+ * Gets and/or sets the configuration of the Interactivity API for a given
+ * store namespace.
+ *
+ * If configuration for that store namespace exists, it merges the new
+ * provided configuration with the existing one.
+ *
+ * @since 6.5.0
+ *
+ * @param string $store_namespace The unique store namespace identifier.
+ * @param array $config Optional. The array that will be merged with the existing configuration for the
+ * specified store namespace.
+ * @return array The current configuration for the specified store namespace.
+ */
+ public function config( string $store_namespace, array $config = null ): array {
+ if ( ! isset( $this->config_data[ $store_namespace ] ) ) {
+ $this->config_data[ $store_namespace ] = array();
+ }
+ if ( is_array( $config ) ) {
+ $this->config_data[ $store_namespace ] = array_replace_recursive(
+ $this->config_data[ $store_namespace ],
+ $config
+ );
+ }
+ return $this->config_data[ $store_namespace ];
+ }
+
+ /**
+ * Prints the serialized client-side interactivity data.
+ *
+ * Encodes the config and initial state into JSON and prints them inside a
+ * script tag of type "application/json". Once in the browser, the state will
+ * be parsed and used to hydrate the client-side interactivity stores and the
+ * configuration will be available using a `getConfig` utility.
+ *
+ * @since 6.5.0
+ */
+ public function print_client_interactivity_data() {
+ $store = array();
+ $has_state = ! empty( $this->state_data );
+ $has_config = ! empty( $this->config_data );
+
+ if ( $has_state || $has_config ) {
+ if ( $has_config ) {
+ $store['config'] = $this->config_data;
+ }
+ if ( $has_state ) {
+ $store['state'] = $this->state_data;
+ }
+ wp_print_inline_script_tag(
+ wp_json_encode(
+ $store,
+ JSON_HEX_TAG | JSON_HEX_AMP
+ ),
+ array(
+ 'type' => 'application/json',
+ 'id' => 'wp-interactivity-data',
+ )
+ );
+ }
+ }
+
+ /**
+ * Registers the `@wordpress/interactivity` script modules.
+ *
+ * @since 6.5.0
+ */
+ public function register_script_modules() {
+ wp_register_script_module(
+ '@wordpress/interactivity',
+ gutenberg_url( '/build/interactivity/index.min.js' ),
+ array(),
+ defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
+ );
+
+ wp_register_script_module(
+ '@wordpress/interactivity-router',
+ gutenberg_url( '/build/interactivity/router.min.js' ),
+ array( '@wordpress/interactivity' ),
+ defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
+ );
+ }
+
+ /**
+ * Adds the necessary hooks for the Interactivity API.
+ *
+ * @since 6.5.0
+ */
+ public function add_hooks() {
+ add_action( 'wp_footer', array( $this, 'print_client_interactivity_data' ) );
+ }
+
+ /**
+ * Processes the interactivity directives contained within the HTML content
+ * and updates the markup accordingly.
+ *
+ * @since 6.5.0
+ *
+ * @param string $html The HTML content to process.
+ * @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags.
+ */
+ public function process_directives( string $html ): string {
+ $p = new WP_Interactivity_API_Directives_Processor( $html );
+ $tag_stack = array();
+ $namespace_stack = array();
+ $context_stack = array();
+ $unbalanced = false;
+
+ $directive_processor_prefixes = array_keys( self::$directive_processors );
+ $directive_processor_prefixes_reversed = array_reverse( $directive_processor_prefixes );
+
+ while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) && false === $unbalanced ) {
+ $tag_name = $p->get_tag();
+
+ if ( $p->is_tag_closer() ) {
+ list( $opening_tag_name, $directives_prefixes ) = end( $tag_stack );
+
+ if ( 0 === count( $tag_stack ) || $opening_tag_name !== $tag_name ) {
+
+ /*
+ * If the tag stack is empty or the matching opening tag is not the
+ * same than the closing tag, it means the HTML is unbalanced and it
+ * stops processing it.
+ */
+ $unbalanced = true;
+ continue;
+ } else {
+
+ /*
+ * It removes the last tag from the stack.
+ */
+ array_pop( $tag_stack );
+
+ /*
+ * If the matching opening tag didn't have any directives, it can skip
+ * the processing.
+ */
+ if ( 0 === count( $directives_prefixes ) ) {
+ continue;
+ }
+ }
+ } else {
+ $directives_prefixes = array();
+
+ foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) {
+
+ /*
+ * Extracts the directive prefix to see if there is a server directive
+ * processor registered for that directive.
+ */
+ list( $directive_prefix ) = $this->extract_prefix_and_suffix( $attribute_name );
+ if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) {
+ $directives_prefixes[] = $directive_prefix;
+ }
+ }
+
+ /*
+ * If this is not a void element, it adds it to the tag stack so it can
+ * process its closing tag and check for unbalanced tags.
+ */
+ if ( ! $p->is_void() ) {
+ $tag_stack[] = array( $tag_name, $directives_prefixes );
+ }
+ }
+
+ /*
+ * Sorts the attributes by the order of the `directives_processor` array
+ * and checks what directives are present in this element. The processing
+ * order is reversed for tag closers.
+ */
+ $directives_prefixes = array_intersect(
+ $p->is_tag_closer()
+ ? $directive_processor_prefixes_reversed
+ : $directive_processor_prefixes,
+ $directives_prefixes
+ );
+
+ // Executes the directive processors present in this element.
+ foreach ( $directives_prefixes as $directive_prefix ) {
+ $func = is_array( self::$directive_processors[ $directive_prefix ] )
+ ? self::$directive_processors[ $directive_prefix ]
+ : array( $this, self::$directive_processors[ $directive_prefix ] );
+ call_user_func_array(
+ $func,
+ array( $p, &$context_stack, &$namespace_stack )
+ );
+ }
+ }
+
+ /*
+ * It returns the original content if the HTML is unbalanced because
+ * unbalanced HTML is not safe to process. In that case, the Interactivity
+ * API runtime will update the HTML on the client side during the hydration.
+ */
+ return $unbalanced || 0 < count( $tag_stack ) ? $html : $p->get_updated_html();
+ }
+
+ /**
+ * Evaluates the reference path passed to a directive based on the current
+ * store namespace, state and context.
+ *
+ * @since 6.5.0
+ *
+ * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute.
+ * @param string $default_namespace The default namespace to use if none is explicitly defined in the directive
+ * value.
+ * @param array|false $context The current context for evaluating the directive or false if there is no
+ * context.
+ * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist.
+ */
+ private function evaluate( $directive_value, string $default_namespace, $context = false ) {
+ list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace );
+ if ( empty( $path ) ) {
+ return null;
+ }
+
+ $store = array(
+ 'state' => isset( $this->state_data[ $ns ] ) ? $this->state_data[ $ns ] : array(),
+ 'context' => isset( $context[ $ns ] ) ? $context[ $ns ] : array(),
+ );
+
+ // Checks if the reference path is preceded by a negator operator (!).
+ $should_negate_value = '!' === $path[0];
+ $path = $should_negate_value ? substr( $path, 1 ) : $path;
+
+ // Extracts the value from the store using the reference path.
+ $path_segments = explode( '.', $path );
+ $current = $store;
+ foreach ( $path_segments as $path_segment ) {
+ if ( isset( $current[ $path_segment ] ) ) {
+ $current = $current[ $path_segment ];
+ } else {
+ return null;
+ }
+ }
+
+ // Returns the opposite if it contains a negator operator (!).
+ return $should_negate_value ? ! $current : $current;
+ }
+
+ /**
+ * Extracts the directive attribute name to separate and return the directive
+ * prefix and an optional suffix.
+ *
+ * The suffix is the string after the first double hyphen and the prefix is
+ * everything that comes before the suffix.
+ *
+ * Example:
+ *
+ * extract_prefix_and_suffix( 'data-wp-interactive' ) => array( 'data-wp-interactive', null )
+ * extract_prefix_and_suffix( 'data-wp-bind--src' ) => array( 'data-wp-bind', 'src' )
+ * extract_prefix_and_suffix( 'data-wp-foo--and--bar' ) => array( 'data-wp-foo', 'and--bar' )
+ *
+ * @since 6.5.0
+ *
+ * @param string $directive_name The directive attribute name.
+ * @return array An array containing the directive prefix and optional suffix.
+ */
+ private function extract_prefix_and_suffix( string $directive_name ): array {
+ return explode( '--', $directive_name, 2 );
+ }
+
+ /**
+ * Parses and extracts the namespace and reference path from the given
+ * directive attribute value.
+ *
+ * If the value doesn't contain an explicit namespace, it returns the
+ * default one. If the value contains a JSON object instead of a reference
+ * path, the function tries to parse it and return the resulting array. If
+ * the value contains strings that reprenset booleans ("true" and "false"),
+ * numbers ("1" and "1.2") or "null", the function also transform them to
+ * regular booleans, numbers and `null`.
+ *
+ * Example:
+ *
+ * extract_directive_value( 'actions.foo', 'myPlugin' ) => array( 'myPlugin', 'actions.foo' )
+ * extract_directive_value( 'otherPlugin::actions.foo', 'myPlugin' ) => array( 'otherPlugin', 'actions.foo' )
+ * extract_directive_value( '{ "isOpen": false }', 'myPlugin' ) => array( 'myPlugin', array( 'isOpen' => false ) )
+ * extract_directive_value( 'otherPlugin::{ "isOpen": false }', 'myPlugin' ) => array( 'otherPlugin', array( 'isOpen' => false ) )
+ *
+ * @since 6.5.0
+ *
+ * @param string|true $directive_value The directive attribute value. It can be `true` when it's a boolean
+ * attribute.
+ * @param string|null $default_namespace Optional. The default namespace if none is explicitly defined.
+ * @return array An array containing the namespace in the first item and the JSON, the reference path, or null on the
+ * second item.
+ */
+ private function extract_directive_value( $directive_value, $default_namespace = null ): array {
+ if ( empty( $directive_value ) || is_bool( $directive_value ) ) {
+ return array( $default_namespace, null );
+ }
+
+ // Replaces the value and namespace if there is a namespace in the value.
+ if ( 1 === preg_match( '/^([\w\-_\/]+)::./', $directive_value ) ) {
+ list($default_namespace, $directive_value) = explode( '::', $directive_value, 2 );
+ }
+
+ /*
+ * Tries to decode the value as a JSON object. If it fails and the value
+ * isn't `null`, it returns the value as it is. Otherwise, it returns the
+ * decoded JSON or null for the string `null`.
+ */
+ $decoded_json = json_decode( $directive_value, true );
+ if ( null !== $decoded_json || 'null' === $directive_value ) {
+ $directive_value = $decoded_json;
+ }
+
+ return array( $default_namespace, $directive_value );
+ }
+
+
+ /**
+ * Processes the `data-wp-interactive` directive.
+ *
+ * It adds the default store namespace defined in the directive value to the
+ * stack so it's available for the nested interactivity elements.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ // In closing tags, it removes the last namespace from the stack.
+ if ( $p->is_tag_closer() ) {
+ return array_pop( $namespace_stack );
+ }
+
+ // Tries to decode the `data-wp-interactive` attribute value.
+ $attribute_value = $p->get_attribute( 'data-wp-interactive' );
+ $decoded_json = is_string( $attribute_value ) && ! empty( $attribute_value )
+ ? json_decode( $attribute_value, true )
+ : null;
+
+ /*
+ * Pushes the newly defined namespace or the current one if the
+ * `data-wp-interactive` definition was invalid or does not contain a
+ * namespace. It does so because the function pops out the current namespace
+ * from the stack whenever it finds a `data-wp-interactive`'s closing tag,
+ * independently of whether the previous `data-wp-interactive` definition
+ * contained a valid namespace.
+ */
+ $namespace_stack[] = isset( $decoded_json['namespace'] )
+ ? $decoded_json['namespace']
+ : end( $namespace_stack );
+ }
+
+ /**
+ * Processes the `data-wp-context` directive.
+ *
+ * It adds the context defined in the directive value to the stack so it's
+ * available for the nested interactivity elements.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ // In closing tags, it removes the last context from the stack.
+ if ( $p->is_tag_closer() ) {
+ return array_pop( $context_stack );
+ }
+
+ $attribute_value = $p->get_attribute( 'data-wp-context' );
+ $namespace_value = end( $namespace_stack );
+
+ // Separates the namespace from the context JSON object.
+ list( $namespace_value, $decoded_json ) = is_string( $attribute_value ) && ! empty( $attribute_value )
+ ? $this->extract_directive_value( $attribute_value, $namespace_value )
+ : array( $namespace_value, null );
+
+ /*
+ * If there is a namespace, it adds a new context to the stack merging the
+ * previous context with the new one.
+ */
+ if ( is_string( $namespace_value ) ) {
+ array_push(
+ $context_stack,
+ array_replace_recursive(
+ end( $context_stack ) !== false ? end( $context_stack ) : array(),
+ array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() )
+ )
+ );
+ } else {
+ /*
+ * If there is no namespace, it pushes the current context to the stack.
+ * It needs to do so because the function pops out the current context
+ * from the stack whenever it finds a `data-wp-context`'s closing tag.
+ */
+ array_push( $context_stack, end( $context_stack ) );
+ }
+ }
+
+ /**
+ * Processes the `data-wp-bind` directive.
+ *
+ * It updates or removes the bound attributes based on the evaluation of its
+ * associated reference.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ if ( ! $p->is_tag_closer() ) {
+ $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' );
+
+ foreach ( $all_bind_directives as $attribute_name ) {
+ list( , $bound_attribute ) = $this->extract_prefix_and_suffix( $attribute_name );
+ if ( empty( $bound_attribute ) ) {
+ return;
+ }
+
+ $attribute_value = $p->get_attribute( $attribute_name );
+ $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) );
+
+ if ( null !== $result && ( false !== $result || '-' === $bound_attribute[4] ) ) {
+ /*
+ * If the result of the evaluation is a boolean and the attribute is
+ * `aria-` or `data-, convert it to a string "true" or "false". It
+ * follows the exact same logic as Preact because it needs to
+ * replicate what Preact will later do in the client:
+ * https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136
+ */
+ if ( is_bool( $result ) && '-' === $bound_attribute[4] ) {
+ $result = $result ? 'true' : 'false';
+ }
+ $p->set_attribute( $bound_attribute, $result );
+ } else {
+ $p->remove_attribute( $bound_attribute );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Processes the `data-wp-class` directive.
+ *
+ * It adds or removes CSS classes in the current HTML element based on the
+ * evaluation of its associated references.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ if ( ! $p->is_tag_closer() ) {
+ $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' );
+
+ foreach ( $all_class_directives as $attribute_name ) {
+ list( , $class_name ) = $this->extract_prefix_and_suffix( $attribute_name );
+ if ( empty( $class_name ) ) {
+ return;
+ }
+
+ $attribute_value = $p->get_attribute( $attribute_name );
+ $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) );
+
+ if ( $result ) {
+ $p->add_class( $class_name );
+ } else {
+ $p->remove_class( $class_name );
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes the `data-wp-style` directive.
+ *
+ * It updates the style attribute value of the current HTML element based on
+ * the evaluation of its associated references.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ if ( ! $p->is_tag_closer() ) {
+ $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' );
+
+ foreach ( $all_style_attributes as $attribute_name ) {
+ list( , $style_property ) = $this->extract_prefix_and_suffix( $attribute_name );
+ if ( empty( $style_property ) ) {
+ continue;
+ }
+
+ $directive_attribute_value = $p->get_attribute( $attribute_name );
+ $style_property_value = $this->evaluate( $directive_attribute_value, end( $namespace_stack ), end( $context_stack ) );
+ $style_attribute_value = $p->get_attribute( 'style' );
+ $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : '';
+
+ /*
+ * Checks first if the style property is not falsy and the style
+ * attribute value is not empty because if it is, it doesn't need to
+ * update the attribute value.
+ */
+ if ( $style_property_value || ( ! $style_property_value && $style_attribute_value ) ) {
+ $style_attribute_value = $this->set_style_property( $style_attribute_value, $style_property, $style_property_value );
+ /*
+ * If the style attribute value is not empty, it sets it. Otherwise,
+ * it removes it.
+ */
+ if ( ! empty( $style_attribute_value ) ) {
+ $p->set_attribute( 'style', $style_attribute_value );
+ } else {
+ $p->remove_attribute( 'style' );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets an individual style property in the `style` attribute of an HTML
+ * element, updating or removing the property when necessary.
+ *
+ * If a property is modified, it is added at the end of the list to make sure
+ * that it overrides the previous ones.
+ *
+ * @since 6.5.0
+ *
+ * Example:
+ *
+ * set_style_property( 'color:green;', 'color', 'red' ) => 'color:red;'
+ * set_style_property( 'background:green;', 'color', 'red' ) => 'background:green;color:red;'
+ * set_style_property( 'color:green;', 'color', null ) => ''
+ *
+ * @param string $style_attribute_value The current style attribute value.
+ * @param string $style_property_name The style property name to set.
+ * @param string|false|null $style_property_value The value to set for the style property. With false, null or an
+ * empty string, it removes the style property.
+ * @return string The new style attribute value after the specified property has been added, updated or removed.
+ */
+ private function set_style_property( string $style_attribute_value, string $style_property_name, $style_property_value ): string {
+ $style_assignments = explode( ';', $style_attribute_value );
+ $result = array();
+ $style_property_value = ! empty( $style_property_value ) ? rtrim( trim( $style_property_value ), ';' ) : null;
+ $new_style_property = $style_property_value ? $style_property_name . ':' . $style_property_value . ';' : '';
+
+ // Generate an array with all the properties but the modified one.
+ foreach ( $style_assignments as $style_assignment ) {
+ if ( empty( trim( $style_assignment ) ) ) {
+ continue;
+ }
+ list( $name, $value ) = explode( ':', $style_assignment );
+ if ( trim( $name ) !== $style_property_name ) {
+ $result[] = trim( $name ) . ':' . trim( $value ) . ';';
+ }
+ }
+
+ // Add the new/modified property at the end of the list.
+ array_push( $result, $new_style_property );
+
+ return implode( '', $result );
+ }
+
+ /**
+ * Processes the `data-wp-text` directive.
+ *
+ * It updates the inner content of the current HTML element based on the
+ * evaluation of its associated reference.
+ *
+ * @since 6.5.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @param array $context_stack The reference to the context stack.
+ * @param array $namespace_stack The reference to the store namespace stack.
+ */
+ private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
+ if ( ! $p->is_tag_closer() ) {
+ $attribute_value = $p->get_attribute( 'data-wp-text' );
+ $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) );
+
+ /*
+ * Follows the same logic as Preact in the client and only changes the
+ * content if the value is a string or a number. Otherwise, it removes the
+ * content.
+ */
+ if ( is_string( $result ) || is_numeric( $result ) ) {
+ $p->set_content_between_balanced_tags( esc_html( $result ) );
+ } else {
+ $p->set_content_between_balanced_tags( '' );
+ }
+ }
+ }
+ }
+
+}
diff --git a/lib/compat/wordpress-6.5/interactivity-api/interactivity-api.php b/lib/compat/wordpress-6.5/interactivity-api/interactivity-api.php
new file mode 100644
index 00000000000000..cd7ca7fb902870
--- /dev/null
+++ b/lib/compat/wordpress-6.5/interactivity-api/interactivity-api.php
@@ -0,0 +1,144 @@
+get_registered( $block_name );
+
+ if ( isset( $block_name ) && isset( $block_type->supports['interactivity'] ) && $block_type->supports['interactivity'] ) {
+ // Annotates the root interactive block for processing.
+ $root_interactive_block = array( $block_name, md5( serialize( $parsed_block ) ) );
+
+ /*
+ * Adds a filter to process the root interactive block once it has
+ * finished rendering.
+ */
+ $process_interactive_blocks = static function ( $content, $parsed_block ) use ( &$root_interactive_block, &$process_interactive_blocks ) {
+ // Checks whether the current block is the root interactive block.
+ list($root_block_name, $root_block_md5) = $root_interactive_block;
+ if ( $root_block_name === $parsed_block['blockName'] && md5( serialize( $parsed_block ) ) === $root_block_md5 ) {
+ // The root interactive blocks has finished rendering, process it.
+ $content = wp_interactivity_process_directives( $content );
+ // Removes the filter and reset the root interactive block.
+ remove_filter( 'render_block', $process_interactive_blocks );
+ $root_interactive_block = null;
+ }
+ return $content;
+ };
+
+ /*
+ * Uses a priority of 20 to ensure that other filters can add additional
+ * directives before the processing starts.
+ */
+ add_filter( 'render_block', $process_interactive_blocks, 20, 2 );
+ }
+ }
+
+ return $parsed_block;
+ }
+ add_filter( 'render_block_data', 'wp_interactivity_process_directives_of_interactive_blocks', 10, 1 );
+}
+
+if ( ! function_exists( 'wp_interactivity' ) ) {
+ /**
+ * Retrieves the main WP_Interactivity_API instance.
+ *
+ * It provides access to the WP_Interactivity_API instance, creating one if it
+ * doesn't exist yet. It also registers the hooks and necessary script
+ * modules.
+ *
+ * @since 6.5.0
+ *
+ * @return WP_Interactivity_API The main WP_Interactivity_API instance.
+ */
+ function wp_interactivity() {
+ static $instance = null;
+ if ( is_null( $instance ) ) {
+ $instance = new WP_Interactivity_API();
+ $instance->add_hooks();
+ $instance->register_script_modules();
+ }
+ return $instance;
+ }
+}
+
+if ( ! function_exists( 'wp_interactivity_process_directives' ) ) {
+ /**
+ * Processes the interactivity directives contained within the HTML content
+ * and updates the markup accordingly.
+ *
+ * @since 6.5.0
+ *
+ * @param string $html The HTML content to process.
+ * @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags.
+ */
+ function wp_interactivity_process_directives( $html ) {
+ return wp_interactivity()->process_directives( $html );
+ }
+}
+
+if ( ! function_exists( 'wp_interactivity_state' ) ) {
+ /**
+ * Gets and/or sets the initial state of an Interactivity API store for a
+ * given namespace.
+ *
+ * If state for that store namespace already exists, it merges the new
+ * provided state with the existing one.
+ *
+ * @since 6.5.0
+ *
+ * @param string $store_namespace The unique store namespace identifier.
+ * @param array $state Optional. The array that will be merged with the existing state for the specified
+ * store namespace.
+ * @return array The current state for the specified store namespace.
+ */
+ function wp_interactivity_state( $store_namespace, $state = null ) {
+ return wp_interactivity()->state( $store_namespace, $state );
+ }
+}
+
+if ( ! function_exists( 'wp_interactivity_config' ) ) {
+ /**
+ * Gets and/or sets the configuration of the Interactivity API for a given
+ * store namespace.
+ *
+ * If configuration for that store namespace exists, it merges the new
+ * provided configuration with the existing one.
+ *
+ * @since 6.5.0
+ *
+ * @param string $store_namespace The unique store namespace identifier.
+ * @param array $config Optional. The array that will be merged with the existing configuration for the
+ * specified store namespace.
+ * @return array The current configuration for the specified store namespace.
+ */
+ function wp_interactivity_config( $store_namespace, $initial_state = null ) {
+ return wp_interactivity()->config( $store_namespace, $initial_state );
+ }
+}
diff --git a/lib/compat/wordpress-6.5/scripts-modules.php b/lib/compat/wordpress-6.5/scripts-modules.php
new file mode 100644
index 00000000000000..ba329b255b1965
--- /dev/null
+++ b/lib/compat/wordpress-6.5/scripts-modules.php
@@ -0,0 +1,119 @@
+add_hooks();
+ }
+ return $instance;
+ }
+}
+
+if ( ! function_exists( 'wp_register_script_module' ) ) {
+ /**
+ * Registers the script module if no script module with that script module
+ * identifier has already been registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module. Should be unique. It will be used in the
+ * final import map.
+ * @param string $src Optional. Full URL of the script module, or path of the script module relative
+ * to the WordPress root directory. If it is provided and the script module has
+ * not been registered yet, it will be registered.
+ * @param array $deps {
+ * Optional. List of dependencies.
+ *
+ * @type string|array $0... {
+ * An array of script module identifiers of the dependencies of this script
+ * module. The dependencies can be strings or arrays. If they are arrays,
+ * they need an `id` key with the script module identifier, and can contain
+ * an `import` key with either `static` or `dynamic`. By default,
+ * dependencies that don't contain an `import` key are considered static.
+ *
+ * @type string $id The script module identifier.
+ * @type string $import Optional. Import type. May be either `static` or
+ * `dynamic`. Defaults to `static`.
+ * }
+ * }
+ * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false.
+ * It is added to the URL as a query string for cache busting purposes. If $version
+ * is set to false, the version number is the currently installed WordPress version.
+ * If $version is set to null, no version is added.
+ */
+ function wp_register_script_module( string $id, string $src, array $deps = array(), $version = false ) {
+ wp_script_modules()->register( $id, $src, $deps, $version );
+ }
+}
+
+if ( ! function_exists( 'wp_enqueue_script_module' ) ) {
+ /**
+ * Marks the script module to be enqueued in the page.
+ *
+ * If a src is provided and the script module has not been registered yet, it
+ * will be registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module. Should be unique. It will be used in the
+ * final import map.
+ * @param string $src Optional. Full URL of the script module, or path of the script module relative
+ * to the WordPress root directory. If it is provided and the script module has
+ * not been registered yet, it will be registered.
+ * @param array $deps {
+ * Optional. List of dependencies.
+ *
+ * @type string|array $0... {
+ * An array of script module identifiers of the dependencies of this script
+ * module. The dependencies can be strings or arrays. If they are arrays,
+ * they need an `id` key with the script module identifier, and can contain
+ * an `import` key with either `static` or `dynamic`. By default,
+ * dependencies that don't contain an `import` key are considered static.
+ *
+ * @type string $id The script module identifier.
+ * @type string $import Optional. Import type. May be either `static` or
+ * `dynamic`. Defaults to `static`.
+ * }
+ * }
+ * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false.
+ * It is added to the URL as a query string for cache busting purposes. If $version
+ * is set to false, the version number is the currently installed WordPress version.
+ * If $version is set to null, no version is added.
+ */
+ function wp_enqueue_script_module( string $id, string $src = '', array $deps = array(), $version = false ) {
+ wp_script_modules()->enqueue( $id, $src, $deps, $version );
+ }
+}
+
+if ( ! function_exists( 'wp_dequeue_script_module' ) ) {
+ /**
+ * Unmarks the script module so it is no longer enqueued in the page.
+ *
+ * @since 6.5.0
+ *
+ * @param string $id The identifier of the script module.
+ */
+ function wp_dequeue_script_module( string $id ) {
+ wp_script_modules()->dequeue( $id );
+ }
+}
diff --git a/lib/experimental/block-bindings/block-bindings.php b/lib/experimental/block-bindings/block-bindings.php
deleted file mode 100644
index 83a1d6132f5f43..00000000000000
--- a/lib/experimental/block-bindings/block-bindings.php
+++ /dev/null
@@ -1,69 +0,0 @@
-register_source( $source_name, $label, $apply );
- }
-}
-
-/**
- * Retrieves the list of registered block sources.
- *
- * @return array The list of registered block sources.
- */
-if ( ! function_exists( 'wp_block_bindings_get_sources' ) ) {
- function wp_block_bindings_get_sources() {
- return wp_block_bindings()->get_sources();
- }
-}
-
-/**
- * Replaces the HTML content of a block based on the provided source value.
- *
- * @param string $block_content Block Content.
- * @param string $block_name The name of the block to process.
- * @param string $block_attr The attribute of the block we want to process.
- * @param string $source_value The value used to replace the HTML.
- * @return string The modified block content.
- */
-if ( ! function_exists( 'wp_block_bindings_replace_html' ) ) {
- function wp_block_bindings_replace_html( $block_content, $block_name, $block_attr, $source_value ) {
- return wp_block_bindings()->replace_html( $block_content, $block_name, $block_attr, $source_value );
- }
-}
diff --git a/lib/experimental/block-bindings/class-wp-block-bindings.php b/lib/experimental/block-bindings/class-wp-block-bindings.php
deleted file mode 100644
index 7eb443dd367a6f..00000000000000
--- a/lib/experimental/block-bindings/class-wp-block-bindings.php
+++ /dev/null
@@ -1,158 +0,0 @@
-sources[ $source_name ] = array(
- 'label' => $label,
- 'apply' => $apply,
- );
- }
-
- /**
- * Depending on the block attributes, replace the proper HTML based on the value returned by the source.
- *
- * @param string $block_content Block Content.
- * @param string $block_name The name of the block to process.
- * @param string $block_attr The attribute of the block we want to process.
- * @param string $source_value The value used to replace the HTML.
- */
- public function replace_html( $block_content, $block_name, $block_attr, $source_value ) {
- $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
- if ( null === $block_type ) {
- return;
- }
-
- // Depending on the attribute source, the processing will be different.
- switch ( $block_type->attributes[ $block_attr ]['source'] ) {
- case 'html':
- case 'rich-text':
- $block_reader = new WP_HTML_Tag_Processor( $block_content );
-
- // TODO: Support for CSS selectors whenever they are ready in the HTML API.
- // In the meantime, support comma-separated selectors by exploding them into an array.
- $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] );
- // Add a bookmark to the first tag to be able to iterate over the selectors.
- $block_reader->next_tag();
- $block_reader->set_bookmark( 'iterate-selectors' );
-
- // TODO: This shouldn't be needed when the `set_inner_html` function is ready.
- // Store the parent tag and its attributes to be able to restore them later in the button.
- // The button block has a wrapper while the paragraph and heading blocks don't.
- if ( 'core/button' === $block_name ) {
- $button_wrapper = $block_reader->get_tag();
- $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
- $button_wrapper_attrs = array();
- foreach ( $button_wrapper_attribute_names as $name ) {
- $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name );
- }
- }
-
- foreach ( $selectors as $selector ) {
- // If the parent tag, or any of its children, matches the selector, replace the HTML.
- if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag(
- array(
- 'tag_name' => $selector,
- )
- ) ) {
- $block_reader->release_bookmark( 'iterate-selectors' );
-
- // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
- // Until then, it is hardcoded for the paragraph, heading, and button blocks.
- // Store the tag and its attributes to be able to restore them later.
- $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
- $selector_attrs = array();
- foreach ( $selector_attribute_names as $name ) {
- $selector_attrs[ $name ] = $block_reader->get_attribute( $name );
- }
- $selector_markup = "<$selector>" . esc_html( $source_value ) . "$selector>";
- $amended_content = new WP_HTML_Tag_Processor( $selector_markup );
- $amended_content->next_tag();
- foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
- $amended_content->set_attribute( $attribute_key, $attribute_value );
- }
- if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
- return $amended_content->get_updated_html();
- }
- if ( 'core/button' === $block_name ) {
- $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}$button_wrapper>";
- $amended_button = new WP_HTML_Tag_Processor( $button_markup );
- $amended_button->next_tag();
- foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
- $amended_button->set_attribute( $attribute_key, $attribute_value );
- }
- return $amended_button->get_updated_html();
- }
- } else {
- $block_reader->seek( 'iterate-selectors' );
- }
- }
- $block_reader->release_bookmark( 'iterate-selectors' );
- return $block_content;
-
- case 'attribute':
- $amended_content = new WP_HTML_Tag_Processor( $block_content );
- if ( ! $amended_content->next_tag(
- array(
- // TODO: build the query from CSS selector.
- 'tag_name' => $block_type->attributes[ $block_attr ]['selector'],
- )
- ) ) {
- return $block_content;
- }
- $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) );
- return $amended_content->get_updated_html();
- break;
-
- default:
- return $block_content;
- break;
- }
- return;
- }
-
- /**
- * Retrieves the list of registered block sources.
- *
- * @return array The array of registered sources.
- */
- public function get_sources() {
- return $this->sources;
- }
-}
diff --git a/lib/experimental/block-bindings/index.php b/lib/experimental/block-bindings/index.php
deleted file mode 100644
index dc9a6c9b96957b..00000000000000
--- a/lib/experimental/block-bindings/index.php
+++ /dev/null
@@ -1,19 +0,0 @@
-attributes, array( 'metadata', 'id' ), false ) ) {
- return null;
- }
- $block_id = $block_instance->attributes['metadata']['id'];
- return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null );
- };
- wp_block_bindings_register_source(
- 'pattern_attributes',
- __( 'Pattern Attributes', 'gutenberg' ),
- $pattern_source_callback
- );
-}
diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php
index 93b65f95fc61ae..fc67f2c9d43770 100644
--- a/lib/experimental/blocks.php
+++ b/lib/experimental/blocks.php
@@ -77,92 +77,3 @@ function wp_enqueue_block_view_script( $block_name, $args ) {
add_filter( 'render_block', $callback, 10, 2 );
}
}
-
-
-
-
-$gutenberg_experiments = get_option( 'gutenberg-experiments' );
-if ( $gutenberg_experiments && (
- array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) ||
- array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments )
-) ) {
-
- require_once __DIR__ . '/block-bindings/index.php';
-
- if ( ! function_exists( 'gutenberg_process_block_bindings' ) ) {
- /**
- * Process the block bindings attribute.
- *
- * @param string $block_content Block Content.
- * @param array $block Block attributes.
- * @param WP_Block $block_instance The block instance.
- */
- function gutenberg_process_block_bindings( $block_content, $block, $block_instance ) {
-
- // Allowed blocks that support block bindings.
- // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes?
- $allowed_blocks = array(
- 'core/paragraph' => array( 'content' ),
- 'core/heading' => array( 'content' ),
- 'core/image' => array( 'url', 'title', 'alt' ),
- 'core/button' => array( 'url', 'text' ),
- );
-
- // If the block doesn't have the bindings property or isn't one of the allowed block types, return.
- if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) {
- return $block_content;
- }
-
- // Assuming the following format for the bindings property of the "metadata" attribute:
- //
- // "bindings": {
- // "title": {
- // "source": {
- // "name": "post_meta",
- // "attributes": { "value": "text_custom_field" }
- // }
- // },
- // "url": {
- // "source": {
- // "name": "post_meta",
- // "attributes": { "value": "text_custom_field" }
- // }
- // }
- // }
- //
-
- $block_bindings_sources = wp_block_bindings_get_sources();
- $modified_block_content = $block_content;
- foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) {
-
- // If the attribute is not in the list, process next attribute.
- if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) {
- continue;
- }
- // If no source is provided, or that source is not registered, process next attribute.
- if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) {
- continue;
- }
-
- $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply'];
- // Get the value based on the source.
- if ( ! isset( $binding_source['source']['attributes'] ) ) {
- $source_args = array();
- } else {
- $source_args = $binding_source['source']['attributes'];
- }
- $source_value = $source_callback( $source_args, $block_instance, $binding_attribute );
- // If the value is null, process next attribute.
- if ( is_null( $source_value ) ) {
- continue;
- }
-
- // Process the HTML based on the block and the attribute.
- $modified_block_content = wp_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value );
- }
- return $modified_block_content;
- }
- }
-
- add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 );
-}
diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php
index 729376cf030dd9..37774e07b27691 100644
--- a/lib/experimental/editor-settings.php
+++ b/lib/experimental/editor-settings.php
@@ -25,18 +25,9 @@ function gutenberg_enable_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-group-grid-variation', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' );
}
-
- if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) ) {
- wp_add_inline_script( 'wp-block-editor', 'window.__experimentalBlockBindings = true', 'before' );
- }
-
if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) {
wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' );
}
-
- if ( $gutenberg_experiments && array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) {
- wp_add_inline_script( 'wp-block-editor', 'window.__experimentalPatternPartialSyncing = true', 'before' );
- }
}
add_action( 'admin_init', 'gutenberg_enable_experiments' );
diff --git a/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php b/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php
deleted file mode 100644
index 8c859769875230..00000000000000
--- a/lib/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php
+++ /dev/null
@@ -1,96 +0,0 @@
-variation_property_defaults = apply_filters( 'wp_webfont_variation_defaults', $this->variation_property_defaults );
-
- /**
- * Fires when the WP_Webfonts instance is initialized.
- *
- * @since X.X.X
- *
- * @param WP_Web_Fonts $wp_webfonts WP_Web_Fonts instance (passed by reference).
- */
- do_action_ref_array( 'wp_default_webfonts', array( &$this ) );
- }
-
- /**
- * Get the list of registered providers.
- *
- * @since X.X.X
- *
- * @return array $providers {
- * An associative array of registered providers, keyed by their unique ID.
- *
- * @type string $provider_id => array {
- * An associate array of provider's class name and fonts.
- *
- * @type string $class_name Fully qualified name of the provider's class.
- * @type string[] $fonts An array of enqueued font handles for this provider.
- * }
- * }
- */
- public function get_providers() {
- return $this->providers;
- }
-
- /**
- * Register a provider.
- *
- * @since X.X.X
- *
- * @param string $provider_id The provider's unique ID.
- * @param string $class_name The provider class name.
- * @return bool True if successfully registered, else false.
- */
- public function register_provider( $provider_id, $class_name ) {
- if ( empty( $provider_id ) || empty( $class_name ) || ! class_exists( $class_name ) ) {
- return false;
- }
-
- $this->providers[ $provider_id ] = array(
- 'class' => $class_name,
- 'fonts' => array(),
- );
- return true;
- }
-
- /**
- * Get the list of all registered font family handles.
- *
- * @since X.X.X
- *
- * @return string[]
- */
- public function get_registered_font_families() {
- $font_families = array();
- foreach ( $this->registered as $handle => $obj ) {
- if ( $obj->extra['is_font_family'] ) {
- $font_families[] = $handle;
- }
- }
- return $font_families;
- }
-
- /**
- * Get the list of all registered font families and their variations.
- *
- * @since X.X.X
- *
- * @return string[]
- */
- public function get_registered() {
- return array_keys( $this->registered );
- }
-
- /**
- * Get the list of enqueued font families and their variations.
- *
- * @since X.X.X
- *
- * @return array[]
- */
- public function get_enqueued() {
- return $this->queue;
- }
-
- /**
- * Registers a font family.
- *
- * @since X.X.X
- *
- * @param string $font_family Font family name to register.
- * @return string|null Font family handle when registration successes. Null on failure.
- */
- public function add_font_family( $font_family ) {
- $font_family_handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family );
- if ( ! $font_family_handle ) {
- return null;
- }
-
- if ( isset( $this->registered[ $font_family_handle ] ) ) {
- return $font_family_handle;
- }
-
- $registered = $this->add( $font_family_handle, false );
- if ( ! $registered ) {
- return null;
- }
-
- $this->add_data( $font_family_handle, 'font-properties', array( 'font-family' => $font_family ) );
- $this->add_data( $font_family_handle, 'is_font_family', true );
-
- return $font_family_handle;
- }
-
- /**
- * Removes a font family and all registered variations.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle The font family to remove.
- */
- public function remove_font_family( $font_family_handle ) {
- if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
- return;
- }
-
- $variations = $this->registered[ $font_family_handle ]->deps;
-
- foreach ( $variations as $variation ) {
- $this->remove( $variation );
- }
-
- $this->remove( $font_family_handle );
- }
-
- /**
- * Add a variation to an existing family or register family if none exists.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- * @param string $variation_handle Optional. The variation's handle. When none is provided, the
- * handle will be dynamically generated.
- * Default empty string.
- * @return string|null Variation handle on success. Else null.
- */
- public function add_variation( $font_family_handle, array $variation, $variation_handle = '' ) {
- if ( ! WP_Fonts_Utils::is_defined( $font_family_handle ) ) {
- trigger_error( 'Font family handle must be a non-empty string.' );
- return null;
- }
-
- // When there is a variation handle, check it.
- if ( '' !== $variation_handle && ! WP_Fonts_Utils::is_defined( $variation_handle ) ) {
- trigger_error( 'Variant handle must be a non-empty string.' );
- return null;
- }
-
- // Register the font family when it does not yet exist.
- if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
- if ( ! $this->add_font_family( $font_family_handle ) ) {
- return null;
- }
- }
-
- $variation = $this->validate_variation( $variation );
-
- // Variation validation failed.
- if ( ! $variation ) {
- return null;
- }
-
- // When there's no variation handle, attempt to create one.
- if ( '' === $variation_handle ) {
- $variation_handle = WP_Fonts_Utils::convert_variation_into_handle( $font_family_handle, $variation );
- if ( is_null( $variation_handle ) ) {
- return null;
- }
- }
-
- // Bail out if the variant is already registered.
- if ( $this->is_variation_registered( $font_family_handle, $variation_handle ) ) {
- return $variation_handle;
- }
-
- $variation_src = array_key_exists( 'src', $variation ) ? $variation['src'] : false;
- $result = $this->add( $variation_handle, $variation_src );
-
- // Bail out if the registration failed.
- if ( ! $result ) {
- return null;
- }
-
- $this->add_data( $variation_handle, 'font-properties', $variation );
- $this->add_data( $variation_handle, 'is_font_family', false );
-
- // Add the font variation as a dependency to the registered font family.
- $this->add_dependency( $font_family_handle, $variation_handle );
-
- $this->providers[ $variation['provider'] ]['fonts'][] = $variation_handle;
-
- return $variation_handle;
- }
-
- /**
- * Removes a variation.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle The font family for this variation.
- * @param string $variation_handle The variation's handle to remove.
- */
- public function remove_variation( $font_family_handle, $variation_handle ) {
- if ( isset( $this->registered[ $variation_handle ] ) ) {
- $this->remove( $variation_handle );
- }
-
- if ( ! $this->is_variation_registered( $font_family_handle, $variation_handle ) ) {
- return;
- }
-
- // Remove the variation as a dependency from its font family.
- $this->registered[ $font_family_handle ]->deps = array_values(
- array_diff(
- $this->registered[ $font_family_handle ]->deps,
- array( $variation_handle )
- )
- );
- }
-
- /**
- * Checks if the variation is registered.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param string $variation_handle Variation's handle.
- * @return bool True when registered to the given font family. Else false.
- */
- private function is_variation_registered( $font_family_handle, $variation_handle ) {
- if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
- return false;
- }
-
- return in_array( $variation_handle, $this->registered[ $font_family_handle ]->deps, true );
- }
-
- /**
- * Adds a variation as a dependency to the given font family.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param string $variation_handle The variation's handle.
- */
- private function add_dependency( $font_family_handle, $variation_handle ) {
- $this->registered[ $font_family_handle ]->deps[] = $variation_handle;
- }
-
- /**
- * Validates and sanitizes a variation.
- *
- * @since X.X.X
- *
- * @param array $variation Variation properties to add.
- * @return false|array Validated variation on success. Else, false.
- */
- private function validate_variation( $variation ) {
- $variation = wp_parse_args( $variation, $this->variation_property_defaults );
-
- // Check the font-family.
- if ( empty( $variation['font-family'] ) || ! is_string( $variation['font-family'] ) ) {
- trigger_error( 'Webfont font-family must be a non-empty string.' );
- return false;
- }
-
- // Local fonts need a "src".
- if ( 'local' === $variation['provider'] ) {
- // Make sure that local fonts have 'src' defined.
- if ( empty( $variation['src'] ) || ( ! is_string( $variation['src'] ) && ! is_array( $variation['src'] ) ) ) {
- trigger_error( 'Webfont src must be a non-empty string or an array of strings.' );
- return false;
- }
- } elseif ( ! isset( $this->providers[ $variation['provider'] ] ) ) {
- trigger_error( sprintf( 'The provider "%s" is not registered', $variation['provider'] ) );
- return false;
- } elseif ( ! class_exists( $this->providers[ $variation['provider'] ]['class'] ) ) {
- trigger_error( sprintf( 'The provider class "%s" does not exist', $variation['provider'] ) );
- return false;
- }
-
- // Validate the 'src' property.
- if ( ! empty( $variation['src'] ) ) {
- foreach ( (array) $variation['src'] as $src ) {
- if ( empty( $src ) || ! is_string( $src ) ) {
- trigger_error( 'Each webfont src must be a non-empty string.' );
- return false;
- }
- }
- }
-
- // Check the font-weight.
- if ( ! is_string( $variation['font-weight'] ) && ! is_int( $variation['font-weight'] ) ) {
- trigger_error( 'Webfont font-weight must be a properly formatted string or integer.' );
- return false;
- }
-
- // Check the font-display.
- if ( ! in_array( $variation['font-display'], array( 'auto', 'block', 'fallback', 'swap', 'optional' ), true ) ) {
- $variation['font-display'] = 'fallback';
- }
-
- $valid_props = array(
- 'ascent-override',
- 'descent-override',
- 'font-display',
- 'font-family',
- 'font-stretch',
- 'font-style',
- 'font-weight',
- 'font-variant',
- 'font-feature-settings',
- 'font-variation-settings',
- 'line-gap-override',
- 'size-adjust',
- 'src',
- 'unicode-range',
-
- // Exceptions.
- 'provider',
- );
-
- foreach ( $variation as $prop => $value ) {
- if ( ! in_array( $prop, $valid_props, true ) ) {
- unset( $variation[ $prop ] );
- }
- }
-
- return $variation;
- }
-
- /**
- * Processes the items and dependencies.
- *
- * Processes the items passed to it or the queue, and their dependencies.
- *
- * @since X.X.X
- *
- * @param string|string[]|bool $handles Optional. Items to be processed: queue (false),
- * single item (string), or multiple items (array of strings).
- * Default false.
- * @param int|false $group Optional. Group level: level (int), no group (false).
- *
- * @return array|string[] Array of web font handles that have been processed.
- * An empty array if none were processed.
- */
- public function do_items( $handles = false, $group = false ) {
- $handles = $this->prepare_handles_for_printing( $handles );
-
- if ( empty( $handles ) ) {
- return $this->done;
- }
-
- $this->all_deps( $handles );
- if ( empty( $this->to_do ) ) {
- return $this->done;
- }
-
- $this->to_do_keyed_handles = array_flip( $this->to_do );
-
- foreach ( $this->get_providers() as $provider_id => $provider ) {
- // Alert and skip if the provider class does not exist.
- if ( ! class_exists( $provider['class'] ) ) {
- /* translators: %s is the provider name. */
- trigger_error(
- sprintf(
- 'Class "%s" not found for "%s" web font provider',
- $provider['class'],
- $provider_id
- )
- );
- continue;
- }
-
- $this->do_item( $provider_id, $group );
- }
-
- $this->process_font_families_after_printing( $handles );
-
- return $this->done;
- }
-
- /**
- * Prepares the given handles for printing.
- *
- * @since X.X.X
- *
- * @param string|string[]|bool $handles Optional. Handles to prepare.
- * Default false.
- * @return array Array of handles.
- */
- private function prepare_handles_for_printing( $handles = false ) {
- if ( false !== $handles ) {
- $handles = $this->validate_handles( $handles );
- // Bail out when invalid.
- if ( empty( $handles ) ) {
- return array();
- }
- }
-
- // Use the enqueued queue.
- if ( empty( $handles ) ) {
- if ( empty( $this->queue ) ) {
- return array();
- }
- $handles = $this->queue;
- }
-
- return $handles;
- }
-
- /**
- * Validates handle(s) to ensure each is a non-empty string.
- *
- * @since X.X.X
- *
- * @param string|string[] $handles Handles to prepare.
- * @return string[]|null Array of handles on success. Else null.
- */
- private function validate_handles( $handles ) {
- // Validate each element is a non-empty string handle.
- $handles = array_filter( (array) $handles, array( WP_Fonts_Utils::class, 'is_defined' ) );
-
- if ( empty( $handles ) ) {
- trigger_error( 'Handles must be a non-empty string or array of non-empty strings' );
- return null;
- }
-
- return $handles;
- }
-
- /**
- * Invokes each provider to process and print its styles.
- *
- * @since X.X.X
- *
- * @see WP_Dependencies::do_item()
- *
- * @param string $provider_id The provider to process.
- * @param int|false $group Not used.
- * @return bool
- */
- public function do_item( $provider_id, $group = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- // Bail out if the provider is not registered.
- if ( ! isset( $this->providers[ $provider_id ] ) ) {
- return false;
- }
-
- $font_handles = $this->get_enqueued_fonts_for_provider( $provider_id );
- if ( empty( $font_handles ) ) {
- return false;
- }
-
- $properties_by_font = $this->get_font_properties_for_provider( $font_handles );
- if ( empty( $properties_by_font ) ) {
- return false;
- }
-
- // Invoke provider to print its styles.
- $provider = $this->get_provider_instance( $provider_id );
- $provider->set_fonts( $properties_by_font );
- $provider->print_styles();
-
- // Clean up.
- $this->update_queues_for_printed_fonts( $font_handles );
-
- return true;
- }
-
- /**
- * Retrieves a list of enqueued web font variations for a provider.
- *
- * @since X.X.X
- *
- * @param string $provider_id The provider to process.
- * @return array[] Webfonts organized by providers.
- */
- private function get_enqueued_fonts_for_provider( $provider_id ) {
- $providers = $this->get_providers();
-
- if ( empty( $providers[ $provider_id ] ) ) {
- return array();
- }
-
- return array_intersect(
- $providers[ $provider_id ]['fonts'],
- $this->to_do
- );
- }
-
- /**
- * Gets a list of font properties for each of the given font handles.
- *
- * @since X.X.X
- *
- * @param array $font_handles Font handles to get properties.
- * @return array A list of fonts with each font's properties.
- */
- private function get_font_properties_for_provider( array $font_handles ) {
- $font_properties = array();
-
- foreach ( $font_handles as $font_handle ) {
- $properties = $this->get_data( $font_handle, 'font-properties' );
- if ( ! $properties ) {
- continue;
- }
- $font_properties[ $font_handle ] = $properties;
- }
-
- return $font_properties;
- }
-
- /**
- * Gets the instance of the provider from the WP_Webfonts::$provider_instance store.
- *
- * @since X.X.X
- *
- * @param string $provider_id The provider to get.
- * @return object Instance of the provider.
- */
- private function get_provider_instance( $provider_id ) {
- if ( ! isset( $this->provider_instances[ $provider_id ] ) ) {
- $this->provider_instances[ $provider_id ] = new $this->providers[ $provider_id ]['class']();
- }
- return $this->provider_instances[ $provider_id ];
- }
-
- /**
- * Update queues for the given printed fonts.
- *
- * @since X.X.X
- *
- * @param array $font_handles Font handles to get properties.
- */
- private function update_queues_for_printed_fonts( array $font_handles ) {
- foreach ( $font_handles as $font_handle ) {
- $this->set_as_done( $font_handle );
- $this->remove_from_to_do_queues( $font_handle );
- }
- }
-
- /**
- * Processes the font families after printing the variations.
- *
- * For each queued font family:
- *
- * a. if any of their variations were printed, the font family is added to the `done` list.
- * b. removes each from the to_do queues.
- *
- * @since X.X.X
- *
- * @param array $handles Handles to process.
- */
- private function process_font_families_after_printing( array $handles ) {
- foreach ( $handles as $handle ) {
- if (
- ! $this->get_data( $handle, 'is_font_family' ) ||
- ! isset( $this->to_do_keyed_handles[ $handle ] )
- ) {
- continue;
- }
- $font_family = $this->registered[ $handle ];
-
- // Add the font family to `done` list if any of its variations were printed.
- if ( ! empty( $font_family->deps ) ) {
- $processed = array_intersect( $font_family->deps, $this->done );
- if ( ! empty( $processed ) ) {
- $this->set_as_done( $handle );
- }
- }
-
- $this->remove_from_to_do_queues( $handle );
- }
- }
-
- /**
- * Removes the handle from the `to_do` and `to_do_keyed_handles` lists.
- *
- * @since X.X.X
- *
- * @param string $handle Handle to remove.
- */
- private function remove_from_to_do_queues( $handle ) {
- unset(
- $this->to_do[ $this->to_do_keyed_handles[ $handle ] ],
- $this->to_do_keyed_handles[ $handle ]
- );
- }
-
- /**
- * Sets the given handle to done by adding it to the `done` list.
- *
- * @since X.X.X
- *
- * @param string $handle Handle to set as done.
- */
- private function set_as_done( $handle ) {
- if ( ! is_array( $this->done ) ) {
- $this->done = array();
- }
- $this->done[] = $handle;
- }
-
- /**
- * Converts the font family and its variations into theme.json structural format.
- *
- * @since X.X.X
- *
- * @param string $font_family_handle Font family to convert.
- * @return array Webfonts in theme.json structural format.
- */
- public function to_theme_json( $font_family_handle ) {
- if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
- return array();
- }
-
- $font_family_name = $this->registered[ $font_family_handle ]->extra['font-properties']['font-family'];
- $theme_json_format = array(
- 'fontFamily' => str_contains( $font_family_name, ' ' ) ? "'{$font_family_name}'" : $font_family_name,
- 'name' => $font_family_name,
- 'slug' => $font_family_handle,
- 'fontFace' => array(),
- );
-
- foreach ( $this->registered[ $font_family_handle ]->deps as $variation_handle ) {
- if ( ! isset( $this->registered[ $variation_handle ] ) ) {
- continue;
- }
-
- $variation_obj = $this->registered[ $variation_handle ];
- $variation_properties = array( 'origin' => 'gutenberg_wp_fonts_api' );
- foreach ( $variation_obj->extra['font-properties'] as $property_name => $property_value ) {
- $property_in_camelcase = lcfirst( str_replace( '-', '', ucwords( $property_name, '-' ) ) );
- $variation_properties[ $property_in_camelcase ] = $property_value;
- }
- $theme_json_format['fontFace'][ $variation_obj->handle ] = $variation_properties;
- }
-
- return $theme_json_format;
- }
-}
diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php
deleted file mode 100644
index b6fd5d78a1435a..00000000000000
--- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider-local.php
+++ /dev/null
@@ -1,284 +0,0 @@
-style_tag_atts = array( 'type' => 'text/css' );
- }
- }
-
- /**
- * Gets the `@font-face` CSS styles for locally-hosted font files.
- *
- * This method does the following processing tasks:
- * 1. Orchestrates an optimized `src` (with format) for browser support.
- * 2. Generates the `@font-face` for all its webfonts.
- *
- * For example, when given these webfonts:
- *
- * array(
- * 'source-serif-pro.normal.200 900' => array(
- * 'provider' => 'local',
- * 'font_family' => 'Source Serif Pro',
- * 'font_weight' => '200 900',
- * 'font_style' => 'normal',
- * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
- * ),
- * 'source-serif-pro.italic.400 900' => array(
- * 'provider' => 'local',
- * 'font_family' => 'Source Serif Pro',
- * 'font_weight' => '200 900',
- * 'font_style' => 'italic',
- * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2' ),
- * ),
- * )
- *
- *
- * the following `@font-face` styles are generated and returned:
- *
- *
- * @font-face{
- * font-family:"Source Serif Pro";
- * font-style:normal;
- * font-weight:200 900;
- * font-stretch:normal;
- * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2') format('woff2');
- * }
- * @font-face{
- * font-family:"Source Serif Pro";
- * font-style:italic;
- * font-weight:200 900;
- * font-stretch:normal;
- * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2') format('woff2');
- * }
- *
- *
- * @since X.X.X
- *
- * @return string The `@font-face` CSS.
- */
- public function get_css() {
- $css = '';
-
- foreach ( $this->webfonts as $webfont ) {
- // Order the webfont's `src` items to optimize for browser support.
- $webfont = $this->order_src( $webfont );
-
- // Build the @font-face CSS for this webfont.
- $css .= '@font-face{' . $this->build_font_face_css( $webfont ) . '}';
- }
-
- return $css;
- }
-
- /**
- * Order `src` items to optimize for browser support.
- *
- * @since 6.0.0
- *
- * @param array $webfont Webfont to process.
- * @return array
- */
- private function order_src( array $webfont ) {
- if ( ! is_array( $webfont['src'] ) ) {
- $webfont['src'] = (array) $webfont['src'];
- }
-
- $src = array();
- $src_ordered = array();
-
- foreach ( $webfont['src'] as $url ) {
- // Add data URIs first.
- if ( str_starts_with( trim( $url ), 'data:' ) ) {
- $src_ordered[] = array(
- 'url' => $url,
- 'format' => 'data',
- );
- continue;
- }
- $format = pathinfo( $url, PATHINFO_EXTENSION );
- $src[ $format ] = $url;
- }
-
- // Add woff2.
- if ( ! empty( $src['woff2'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['woff2'],
- 'format' => 'woff2',
- );
- }
-
- // Add woff.
- if ( ! empty( $src['woff'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['woff'],
- 'format' => 'woff',
- );
- }
-
- // Add ttf.
- if ( ! empty( $src['ttf'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['ttf'],
- 'format' => 'truetype',
- );
- }
-
- // Add eot.
- if ( ! empty( $src['eot'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['eot'],
- 'format' => 'embedded-opentype',
- );
- }
-
- // Add otf.
- if ( ! empty( $src['otf'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['otf'],
- 'format' => 'opentype',
- );
- }
- $webfont['src'] = $src_ordered;
-
- return $webfont;
- }
-
- /**
- * Builds the font-family's CSS.
- *
- * @since 6.0.0
- *
- * @param array $webfont Webfont to process.
- * @return string This font-family's CSS.
- */
- private function build_font_face_css( array $webfont ) {
- $css = '';
-
- // Wrap font-family in quotes if it contains spaces
- // and is not already wrapped in quotes.
- if (
- str_contains( $webfont['font-family'], ' ' ) &&
- ! str_contains( $webfont['font-family'], '"' ) &&
- ! str_contains( $webfont['font-family'], "'" )
- ) {
- $webfont['font-family'] = '"' . $webfont['font-family'] . '"';
- }
-
- foreach ( $webfont as $key => $value ) {
-
- // Skip "provider", since it's for internal API use,
- // and not a valid CSS property.
- if ( 'provider' === $key ) {
- continue;
- }
-
- // Compile the "src" parameter.
- if ( 'src' === $key ) {
- $value = $this->compile_src( $webfont['font-family'], $value );
- }
-
- // If font-variation-settings is an array, convert it to a string.
- if ( 'font-variation-settings' === $key && is_array( $value ) ) {
- $value = $this->compile_variations( $value );
- }
-
- if ( ! empty( $value ) ) {
- $css .= "$key:$value;";
- }
- }
-
- return $css;
- }
-
- /**
- * Compiles the `src` into valid CSS.
- *
- * @since 6.0.0
- *
- * @param string $font_family Font family.
- * @param array $value Value to process.
- * @return string The CSS.
- */
- private function compile_src( $font_family, array $value ) {
- $src = "local($font_family)";
-
- foreach ( $value as $item ) {
-
- if ( str_starts_with( $item['url'], get_site_url() ) ) {
- $item['url'] = wp_make_link_relative( $item['url'] );
- }
-
- $src .= ( 'data' === $item['format'] )
- ? ", url({$item['url']})"
- : ", url('{$item['url']}') format('{$item['format']}')";
- }
- return $src;
- }
-
- /**
- * Compiles the font variation settings.
- *
- * @since 6.0.0
- *
- * @param array $font_variation_settings Array of font variation settings.
- * @return string The CSS.
- */
- private function compile_variations( array $font_variation_settings ) {
- $variations = '';
-
- foreach ( $font_variation_settings as $key => $value ) {
- $variations .= "$key $value";
- }
-
- return $variations;
- }
-}
diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php
deleted file mode 100644
index 5b7f5ece335b11..00000000000000
--- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-provider.php
+++ /dev/null
@@ -1,66 +0,0 @@
-webfonts = $this->fonts;
- }
-
- /**
- * This method is here to wire WP_Fonts_Provider::do_item() to this
- * deprecated class to ensure the fonts get set.
- *
- * @param array[] $fonts Fonts to be processed.
- */
- public function set_fonts( array $fonts ) {
- parent::set_fonts( $fonts );
- $this->webfonts = $this->fonts;
- }
-}
diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php b/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php
deleted file mode 100644
index d0242acf9a2b6f..00000000000000
--- a/lib/experimental/fonts-api/bc-layer/class-wp-webfonts-utils.php
+++ /dev/null
@@ -1,85 +0,0 @@
-wp_fonts = ! empty( $wp_fonts ) ? $wp_fonts : wp_fonts();
- }
-
- /**
- * Gets the font slug.
- *
- * @since X.X.X
- * @deprecated Use WP_Fonts_Utils::convert_font_family_into_handle() or WP_Fonts_Utils::get_font_family_from_variation().
- *
- * @param array|string $to_convert The value to convert into a slug. Expected as the web font's array
- * or a font-family as a string.
- * @return string|false The font slug on success, or false if the font-family cannot be determined.
- */
- public static function get_font_slug( $to_convert ) {
- $message = is_array( $to_convert )
- ? 'Use WP_Fonts_Utils::get_font_family_from_variation() to get the font family from an array and then WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle'
- : 'Use WP_Fonts_Utils::convert_font_family_into_handle() to convert the font-family name into a handle';
- _deprecated_function( __METHOD__, 'GB 14.9.1', $message );
-
- if ( empty( $to_convert ) ) {
- return false;
- }
-
- $font_family_name = is_array( $to_convert )
- ? WP_Fonts_Utils::get_font_family_from_variation( $to_convert )
- : $to_convert;
-
- $slug = false;
- if ( ! empty( $font_family_name ) ) {
- $slug = WP_Fonts_Utils::convert_font_family_into_handle( $font_family_name );
- }
-
- return $slug;
- }
-
- /**
- * Initializes the API.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_fonts().
- */
- public static function init() {
- _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()' );
- }
-
- /**
- * Get the list of all registered font family handles.
- *
- * @since X.X.X
- * @deprecated GB 15.8.0 Use wp_fonts()->get_registered_font_families().
- *
- * @return string[]
- */
- public function get_registered_font_families() {
- _deprecated_function( __METHOD__, 'GB 15.8.0', 'wp_fonts()->get_registered_font_families()' );
-
- return $this->wp_fonts->get_registered_font_families();
- }
-
- /**
- * Gets the list of registered fonts.
- *
- * @since 6.0.0
- * @deprecated 14.9.1 Use wp_fonts()->get_registered().
- *
- * @return array[]
- */
- public function get_registered_webfonts() {
- _deprecated_function( __METHOD__, '14.9.1', 'wp_fonts()->get_registered()' );
-
- return $this->_get_registered_webfonts();
- }
-
- /**
- * Gets the list of enqueued fonts.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_fonts()->get_enqueued().
- *
- * @return array[]
- */
- public function get_enqueued_webfonts() {
- _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()->get_enqueued()' );
-
- return $this->wp_fonts->queue;
- }
-
- /**
- * Gets the list of all fonts.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_fonts()->get_registered().
- *
- * @return array[]
- */
- public function get_all_webfonts() {
- _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_fonts()->get_registered()' );
-
- return $this->_get_registered_webfonts();
- }
-
- /**
- * Registers a webfont.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_register_fonts().
- *
- * @param array $webfont Web font to register.
- * @param string $font_family_handle Optional. Font family handle for the given variation.
- * Default empty string.
- * @param string $variation_handle Optional. Handle for the variation to register.
- * @param bool $silence_deprecation Optional. Silences the deprecation notice. For internal use.
- * @return string|false The font family slug if successfully registered, else false.
- */
- public function register_webfont( array $webfont, $font_family_handle = '', $variation_handle = '', $silence_deprecation = false ) {
- if ( ! $silence_deprecation ) {
- _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_register_fonts()' );
- }
-
- // Bail out if no variation passed as there's not to register.
- if ( empty( $webfont ) ) {
- return false;
- }
-
- // Restructure definition: keyed by font-family and array of variations.
- $font = array( $webfont );
- if ( WP_Fonts_Utils::is_defined( $font_family_handle ) ) {
- $font = array( $font_family_handle => $font );
- } else {
- $font = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $font, true );
- $font_family_handle = array_key_first( $font );
- }
-
- if ( empty( $font ) || empty( $font_family_handle ) ) {
- return false;
- }
-
- // If the variation handle was passed, add it as variation key.
- if ( WP_Fonts_Utils::is_defined( $variation_handle ) ) {
- $font[ $font_family_handle ] = array( $variation_handle => $font[ $font_family_handle ][0] );
- }
-
- // Register with the Fonts API.
- $handle = wp_register_fonts( $font );
- if ( empty( $handle ) ) {
- return false;
- }
- return array_pop( $handle );
- }
-
- /**
- * Enqueue a font-family that has been already registered.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_enqueue_fonts().
- *
- * @param string $font_family_name The font family name to be enqueued.
- * @return bool True if successfully enqueued, else false.
- */
- public function enqueue_webfont( $font_family_name ) {
- _deprecated_function( __METHOD__, 'GB 14.9.1', 'wp_enqueue_fonts()' );
-
- wp_enqueue_fonts( array( $font_family_name ) );
- return true;
- }
-
- /**
- * Gets the registered webfonts in the original web font property structure keyed by each handle.
- *
- * @return array[]
- */
- private function _get_registered_webfonts() {
- $font_families = array();
- $registered = array();
-
- // Find the registered font families.
- foreach ( $this->wp_fonts->registered as $handle => $obj ) {
- if ( ! $obj->extra['is_font_family'] ) {
- continue;
- }
-
- if ( ! isset( $registered[ $handle ] ) ) {
- $registered[ $handle ] = array();
- }
-
- $font_families[ $handle ] = $obj->deps;
- }
-
- // Build the return array structure.
- foreach ( $font_families as $font_family_handle => $variations ) {
- foreach ( $variations as $variation_handle ) {
- $variation_obj = $this->wp_fonts->registered[ $variation_handle ];
-
- $registered[ $font_family_handle ][ $variation_handle ] = $variation_obj->extra['font-properties'];
- }
- }
-
- return $registered;
- }
-}
diff --git a/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php b/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php
deleted file mode 100644
index 7a3a7bd013eea3..00000000000000
--- a/lib/experimental/fonts-api/bc-layer/webfonts-deprecations.php
+++ /dev/null
@@ -1,260 +0,0 @@
- array[] $variations {
- * An array of web font variations for this font-family.
- * Each variation has the following structure.
- *
- * @type array $variation {
- * @type string $provider The provider ID. Default 'local'.
- * @type string $font-style The font-style property. Default 'normal'.
- * @type string $font-weight The font-weight property. Default '400'.
- * @type string $font-display The font-display property. Default 'fallback'.
- * }
- * }
- * }
- * @return string[] Array of registered font family handles.
- */
- function wp_register_webfonts( array $webfonts ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_register_fonts()' );
-
- $webfonts = Gutenberg_Fonts_API_BC_Layer::migrate_deprecated_structure( $webfonts );
-
- return wp_register_fonts( $webfonts );
- }
-}
-
-if ( ! function_exists( 'wp_register_webfont' ) ) {
- /**
- * Registers a single webfont.
- *
- * Example of how to register Source Serif Pro font with font-weight range of 200-900:
- *
- * If the font file is contained within the theme:
- *
- *
- * wp_register_webfont(
- * array(
- * 'provider' => 'local',
- * 'font-family' => 'Source Serif Pro',
- * 'font-weight' => '200 900',
- * 'font-style' => 'normal',
- * 'src' => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
- * )
- * );
- *
- *
- * @since 6.0.0
- * @deprecated 14.9.1 Use wp_register_fonts().
- *
- * @param array $webfont Webfont to be registered.
- * @return string|false The font family slug if successfully registered, else false.
- */
- function wp_register_webfont( array $webfont ) {
- _deprecated_function( __FUNCTION__, '14.9.1', 'wp_register_fonts()' );
-
- return wp_fonts()->register_webfont( $webfont, '', '', false );
- }
-}
-
-if ( ! function_exists( 'wp_enqueue_webfonts' ) ) {
- /**
- * Enqueues one or more font family and all of its variations.
- *
- * @since X.X.X
- * @since GB 15.1 Use wp_enqueue_fonts() ihstead.
- *
- * @param string[] $font_families Font family(ies) to enqueue.
- */
- function wp_enqueue_webfonts( array $font_families ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_enqueue_fonts()' );
-
- wp_enqueue_fonts( $font_families );
- }
-}
-
-if ( ! function_exists( 'wp_enqueue_webfont' ) ) {
- /**
- * Enqueue a single font family that has been registered beforehand.
- *
- * Example of how to enqueue Source Serif Pro font:
- *
- *
- * wp_enqueue_webfont( 'Source Serif Pro' );
- *
- *
- * Font families should be enqueued from the `init` hook or later.
- *
- * @since 6.0.0
- * @deprecated GB 14.9.1 Use wp_enqueue_fonts() instead.
- *
- * @param string $font_family_name The font family name to be enqueued.
- * @return bool True if successfully enqueued, else false.
- */
- function wp_enqueue_webfont( $font_family_name ) {
- _deprecated_function( __FUNCTION__, 'GB 14.9.1', 'wp_enqueue_fonts()' );
-
- wp_enqueue_fonts( array( $font_family_name ) );
- return true;
- }
-}
-
-if ( ! function_exists( 'wp_enqueue_webfont_variations' ) ) {
- /**
- * Enqueues a specific set of web font variations.
- *
- * @since X.X.X
- * @deprecated GB 15.1 Use wp_enqueue_font_variations() instead.
- *
- * @param string|string[] $variation_handles Variation handle (string) or handles (array of strings).
- */
- function wp_enqueue_webfont_variations( $variation_handles ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_enqueue_font_variations()' );
-
- wp_enqueue_font_variations( $variation_handles );
- }
-}
-
-if ( ! function_exists( 'wp_deregister_webfont_variation' ) ) {
- /**
- * Deregisters a font variation.
- *
- * @since GB 14.9.1
- * @deprecated GB 15.1 Use wp_deregister_font_variation() instead.
- *
- * @param string $font_family_handle The font family for this variation.
- * @param string $variation_handle The variation's handle to remove.
- */
- function wp_deregister_webfont_variation( $font_family_handle, $variation_handle ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_deregister_font_variation()' );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- }
-}
-
-if ( ! function_exists( 'wp_get_webfont_providers' ) ) {
- /**
- * Gets all registered providers.
- *
- * Return an array of providers, each keyed by their unique
- * ID (i.e. the `$id` property in the provider's object) with
- * an instance of the provider (object):
- * ID => provider instance
- *
- * Each provider contains the business logic for how to
- * process its specific font service (i.e. local or remote)
- * and how to generate the `@font-face` styles for its service.
- *
- * @since X.X.X
- * @deprecated GB 14.9.1 Use wp_fonts()->get_providers().
- *
- * @return string[] All registered providers, each keyed by their unique ID.
- */
- function wp_get_webfont_providers() {
- _deprecated_function( __FUNCTION__, '14.9.1', 'wp_fonts()->get_providers()' );
-
- $providers = array();
- foreach ( wp_fonts()->get_providers() as $id => $config ) {
- $providers[ $id ] = $config['class'];
- }
-
- return $providers;
- }
-}
-
-if ( ! function_exists( 'wp_register_webfont_provider' ) ) {
- /**
- * Registers a custom font service provider.
- *
- * A webfont provider contains the business logic for how to
- * interact with a remote font service and how to generate
- * the `@font-face` styles for that remote service.
- *
- * How to register a custom font service provider:
- * 1. Load its class file into memory before registration.
- * 2. Pass the class' name to this function.
- *
- * For example, for a class named `My_Custom_Font_Service_Provider`:
- * ```
- * wp_register_webfont_provider( My_Custom_Font_Service_Provider::class );
- * ```
- *
- * @since X.X.X
- * @deprecated GB 15.1 Use wp_register_font_provider() instead.
- *
- * @param string $name The provider's name.
- * @param string $classname The provider's class name.
- * The class should be a child of `WP_Webfonts_Provider`.
- * See {@see WP_Webfonts_Provider}.
- *
- * @return bool True if successfully registered, else false.
- */
- function wp_register_webfont_provider( $name, $classname ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_register_font_provider' );
-
- return wp_register_font_provider( $name, $classname );
- }
-}
-
-if ( ! function_exists( 'wp_print_webfonts' ) ) {
- /**
- * Invokes each provider to process and print its styles.
- *
- * @since GB 14.9.1
- * @deprecated GB 15.1 Use wp_print_fonts() instead.
- *
- * @param string|string[]|false $handles Optional. Items to be processed: queue (false),
- * single item (string), or multiple items (array of strings).
- * Default false.
- * @return array|string[] Array of web font handles that have been processed.
- * An empty array if none were processed.
- */
- function wp_print_webfonts( $handles = false ) {
- _deprecated_function( __FUNCTION__, 'GB 15.1', 'wp_print_fonts' );
-
- return wp_print_fonts( $handles );
- }
-}
diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php
deleted file mode 100644
index 019861a9ae191f..00000000000000
--- a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php
+++ /dev/null
@@ -1,236 +0,0 @@
-style_tag_atts = array( 'type' => 'text/css' );
- }
- }
-
- /**
- * Gets the `@font-face` CSS styles for locally-hosted font files.
- *
- * This method does the following processing tasks:
- * 1. Orchestrates an optimized `src` (with format) for browser support.
- * 2. Generates the `@font-face` for all its fonts.
- *
- * @since X.X.X
- *
- * @return string The `@font-face` CSS.
- */
- public function get_css() {
- $css = '';
-
- foreach ( $this->fonts as $font ) {
- // Order the font's `src` items to optimize for browser support.
- $font = $this->order_src( $font );
-
- // Build the @font-face CSS for this font.
- $css .= '@font-face{' . $this->build_font_face_css( $font ) . '}';
- }
-
- return $css;
- }
-
- /**
- * Order `src` items to optimize for browser support.
- *
- * @since X.X.X
- *
- * @param array $font Font to process.
- * @return array
- */
- private function order_src( array $font ) {
- if ( ! is_array( $font['src'] ) ) {
- $font['src'] = (array) $font['src'];
- }
-
- $src = array();
- $src_ordered = array();
-
- foreach ( $font['src'] as $url ) {
- // Add data URIs first.
- if ( str_starts_with( trim( $url ), 'data:' ) ) {
- $src_ordered[] = array(
- 'url' => $url,
- 'format' => 'data',
- );
- continue;
- }
- $format = pathinfo( $url, PATHINFO_EXTENSION );
- $src[ $format ] = $url;
- }
-
- // Add woff2.
- if ( ! empty( $src['woff2'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['woff2'],
- 'format' => 'woff2',
- );
- }
-
- // Add woff.
- if ( ! empty( $src['woff'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['woff'],
- 'format' => 'woff',
- );
- }
-
- // Add ttf.
- if ( ! empty( $src['ttf'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['ttf'],
- 'format' => 'truetype',
- );
- }
-
- // Add eot.
- if ( ! empty( $src['eot'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['eot'],
- 'format' => 'embedded-opentype',
- );
- }
-
- // Add otf.
- if ( ! empty( $src['otf'] ) ) {
- $src_ordered[] = array(
- 'url' => $src['otf'],
- 'format' => 'opentype',
- );
- }
- $font['src'] = $src_ordered;
-
- return $font;
- }
-
- /**
- * Builds the font-family's CSS.
- *
- * @since X.X.X
- *
- * @param array $font Font to process.
- * @return string This font-family's CSS.
- */
- private function build_font_face_css( array $font ) {
- $css = '';
-
- // Wrap font-family in quotes if it contains spaces
- // and is not already wrapped in quotes.
- if (
- str_contains( $font['font-family'], ' ' ) &&
- ! str_contains( $font['font-family'], '"' ) &&
- ! str_contains( $font['font-family'], "'" )
- ) {
- $font['font-family'] = '"' . $font['font-family'] . '"';
- }
-
- foreach ( $font as $key => $value ) {
-
- // Skip "provider", since it's for internal API use,
- // and not a valid CSS property.
- if ( 'provider' === $key ) {
- continue;
- }
-
- // Compile the "src" parameter.
- if ( 'src' === $key ) {
- $value = $this->compile_src( $value );
- }
-
- // If font-variation-settings is an array, convert it to a string.
- if ( 'font-variation-settings' === $key && is_array( $value ) ) {
- $value = $this->compile_variations( $value );
- }
-
- if ( ! empty( $value ) ) {
- $css .= "$key:$value;";
- }
- }
-
- return $css;
- }
-
- /**
- * Compiles the `src` into valid CSS.
- *
- * @since X.X.X
- *
- * @param array $value Value to process.
- * @return string The CSS.
- */
- private function compile_src( array $value ) {
- $src = '';
-
- foreach ( $value as $item ) {
- $src .= ( 'data' === $item['format'] )
- ? ", url({$item['url']})"
- : ", url('{$item['url']}') format('{$item['format']}')";
- }
-
- $src = ltrim( $src, ', ' );
- return $src;
- }
-
- /**
- * Compiles the font variation settings.
- *
- * @since X.X.X
- *
- * @param array $font_variation_settings Array of font variation settings.
- * @return string The CSS.
- */
- private function compile_variations( array $font_variation_settings ) {
- $variations = '';
-
- foreach ( $font_variation_settings as $key => $value ) {
- $variations .= "$key $value";
- }
-
- return $variations;
- }
-}
diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider.php b/lib/experimental/fonts-api/class-wp-fonts-provider.php
deleted file mode 100644
index b59c59d6b8938f..00000000000000
--- a/lib/experimental/fonts-api/class-wp-fonts-provider.php
+++ /dev/null
@@ -1,129 +0,0 @@
-fonts = $fonts;
- }
-
- /**
- * Prints the generated styles.
- *
- * @since X.X.X
- */
- public function print_styles() {
- printf(
- $this->get_style_element(),
- $this->get_css()
- );
- }
-
- /**
- * Gets the `@font-face` CSS for the provider's fonts.
- *
- * This method is where the provider does it processing to build the
- * needed `@font-face` CSS for all of its fonts. Specifics of how
- * this processing is done is contained in each provider.
- *
- * @since X.X.X
- *
- * @return string The `@font-face` CSS.
- */
- abstract public function get_css();
-
- /**
- * Gets the `\n";
- }
-
- /**
- * Gets the defined \n",
- $font_faces['merriweather-200-900-normal']
- ),
- ),
- 'print Source Serif Pro for local provider' => array(
- 'setup' => array(
- 'provider' => array( 'local' => $providers['local'] ),
- 'provider_handles' => array( 'local' => $local_font_handles ),
- 'registered' => $local_fonts,
- 'enqueued' => array( 'source-serif-pro' ),
- ),
- 'expected_done' => array( 'source-serif-pro', 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
- 'print all fonts for local provider' => array(
- 'setup' => array(
- 'provider' => array( 'local' => $providers['local'] ),
- 'provider_handles' => array( 'local' => $local_font_handles ),
- 'registered' => $local_fonts,
- 'enqueued' => array( 'merriweather', 'source-serif-pro' ),
- ),
- 'expected_done' => array(
- 'merriweather',
- 'merriweather-200-900-normal',
- 'source-serif-pro',
- 'Source Serif Pro-300-normal',
- 'Source Serif Pro-900-italic',
- ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
-
- // All providers registered with multiple fonts.
-
- 'print font1 when all providers registered' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1' ) ) ),
- 'expected_done' => array( 'font1', 'font1-300-normal', 'font1-300-italic', 'font1-900-normal' ),
- 'expected_output' => sprintf(
- '
%s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal']
- ),
- ),
- 'print all mock fonts when all providers registered' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font1', 'font2', 'font3' ) ) ),
- 'expected_done' => array(
- 'font1',
- 'font1-300-normal',
- 'font1-300-italic',
- 'font1-900-normal',
- 'font2',
- 'font2-200-900-normal',
- 'font2-200-900-italic',
- 'font3',
- 'font3-bold-normal',
- ),
- 'expected_output' => sprintf(
- '
%s; %s; %s; %s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal'],
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic'],
- $font_faces['font3-bold-normal']
- ),
- ),
- 'print merriweather when all providers registered' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather' ) ) ),
- 'expected_done' => array( 'merriweather', 'merriweather-200-900-normal' ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal']
- ),
- ),
- 'print all local fonts when all providers registered' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather', 'source-serif-pro' ) ) ),
- 'expected_done' => array(
- 'merriweather',
- 'merriweather-200-900-normal',
- 'source-serif-pro',
- 'Source Serif Pro-300-normal',
- 'Source Serif Pro-900-italic',
- ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
-
- 'print all fonts for all providers' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => $all_variation_handles ) ),
- 'expected_done' => $all_variation_handles,
- 'expected_output' =>
- sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ) .
- sprintf(
- '
%s; %s; %s; %s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal'],
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic'],
- $font_faces['font3-bold-normal']
- ),
- ),
-
- // Specific variations enqueued.
- // Validates that only these specific variations print once.
-
- 'specific variation: 1 local' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal' ) ) ),
- 'expected_done' => array( 'merriweather-200-900-normal' ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal']
- ),
- ),
- 'specific variation: 1 local from different font families' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ) ),
- 'expected_done' => array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ),
- 'expected_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
- 'specific variation: 1 local and 1 mock' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ) ) ),
- 'expected_done' => array( 'merriweather-200-900-normal', 'font2-200-900-normal' ),
- 'expected_output' => sprintf(
- "\n" .
- '
%s \n',
- $font_faces['merriweather-200-900-normal'],
- $font_faces['font2-200-900-normal']
- ),
- ),
- 'specific variation: 1 mock and 1 local' => array(
- 'setup' => array_merge( $setup_all, array( 'enqueued' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ) ) ),
- 'expected_done' => array( 'font2-200-900-normal', 'Source Serif Pro-300-normal' ),
- 'expected_output' => sprintf(
- "\n" .
- '
%s \n',
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['font2-200-900-normal']
- ),
- ),
- );
- }
-
- protected function get_data_registry() {
- return array(
- 'lato' => array(),
- 'merriweather' => array(
- 'merriweather-200-900-normal' => array(
- 'font-family' => 'Merriweather',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2',
- ),
- ),
- 'Source Serif Pro' => array(
- 'Source Serif Pro-300-normal' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '300',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'Source Serif Pro-900-italic' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'italic',
- 'font-weight' => '900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- ),
- 'my-font' => array(
- 'my-font-300-normal' => array(
- 'font-family' => 'My Font',
- 'font-weight' => '300',
- 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2',
- ),
- 'my-font-300-italic' => array(
- 'font-family' => 'My Font',
- 'font-weight' => '300',
- 'font-style' => 'italic',
- 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2',
- ),
- 'my-font-900-normal' => array(
- 'font-family' => 'My Font',
- 'font-weight' => '900',
- 'src' => 'https://example.com/assets/fonts/my-font.ttf.woff2',
- ),
- ),
- );
- }
-
- /**
- * Gets the provider definitions.
- *
- * @since X.X.X
- *
- * @param string $provider_id Optional. Provider ID to get. Default empty string.
- * @return array
- */
- protected function get_provider_definitions( $provider_id = '' ) {
- $providers = array(
- 'mock' => array(
- 'id' => 'mock',
- 'class' => Mock_Provider::class,
- ),
- 'local' => array(
- 'id' => 'local',
- 'class' => WP_Fonts_Provider_Local::class,
- ),
- );
-
- if ( '' === $provider_id ) {
- return $providers;
- }
-
- if ( isset( $providers[ $provider_id ] ) ) {
- return $providers[ $provider_id ];
- }
-
- return array(
- 'id' => $provider_id,
- 'class' => '',
- );
- }
-
- /**
- * Gets font definitions for both local and mock providers.
- *
- * @since X.X.X
- *
- * @return array|string[][][]
- */
- protected function get_registered_fonts() {
- return array_merge(
- $this->get_registered_local_fonts(),
- $this->get_registered_mock_fonts()
- );
- }
-
- /**
- * Returns an array of font-face styles that matches the font definitions
- * in get_registered_local_fonts() and get_registered_mock_fonts().
- *
- * @since X.X.X
- *
- * @return string[]
- */
- protected function get_registered_fonts_css() {
- return array(
- 'merriweather-200-900-normal' => <<
<< << 'font1-300-normal',
- 'font1-300-italic' => 'font1-300-italic',
- 'font1-900-normal' => 'font1-900-normal',
- 'font2-200-900-normal' => 'font2-200-900-normal',
- 'font2-200-900-italic' => 'font2-200-900-italic',
- 'font3-bold-normal' => 'font3-bold-normal',
- );
- }
-
- /**
- * Gets font definitions for local provider.
- *
- * @since X.X.X
- *
- * @return string[][][]
- */
- protected function get_registered_local_fonts() {
- return array(
- 'lato' => array(),
- 'merriweather' => array(
- 'merriweather-200-900-normal' => array(
- 'provider' => 'local',
- 'font-family' => 'Merriweather',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-display' => 'fallback',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2',
- ),
- ),
- 'Source Serif Pro' => array(
- 'Source Serif Pro-300-normal' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '300',
- 'font-display' => 'fallback',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'Source Serif Pro-900-italic' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'italic',
- 'font-weight' => '900',
- 'font-display' => 'fallback',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- ),
- );
- }
-
- /**
- * Gets font definitions for mock provider.
- *
- * @since X.X.X
- *
- * @return string[][][]
- */
- protected function get_registered_mock_fonts() {
- return array(
- 'font1' => array(
- 'font1-300-normal' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 1',
- 'font-weight' => '300',
- 'font-style' => 'normal',
- 'font-display' => 'fallback',
- ),
- 'font1-300-italic' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 1',
- 'font-weight' => '300',
- 'font-style' => 'italic',
- 'font-display' => 'fallback',
- ),
- 'font1-900-normal' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 1',
- 'font-weight' => '900',
- 'font-style' => 'normal',
- 'font-display' => 'fallback',
- ),
- ),
- 'font2' => array(
- 'font2-200-900-normal' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 2',
- 'font-weight' => '200 900',
- 'font-style' => 'normal',
- 'font-display' => 'fallback',
- ),
- 'font2-200-900-italic' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 2',
- 'font-weight' => '200 900',
- 'font-style' => 'italic',
- 'font-display' => 'fallback',
- ),
- ),
- 'font3' => array(
- 'font3-bold-normal' => array(
- 'provider' => 'mock',
- 'font-family' => 'Font 3',
- 'font-weight' => 'bold',
- 'font-style' => 'normal',
- 'font-display' => 'fallback',
- 'font-stretch' => 'normal',
- ),
- ),
- );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_print_user_selected_fonts() {
- $global_styles = $this->get_mock_user_selected_fonts_global_styles();
- $font_faces = $this->get_registered_fonts_css();
-
- return array(
- 'print font1' => array(
- 'global_styles' => $global_styles['font1'],
- 'expected_done' => array(
- 'font1-300-normal',
- 'font1-300-italic',
- 'font1-900-normal',
- 'font1',
- ),
- 'expected_output' => sprintf(
- '%s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal']
- ),
- ),
- 'print font2' => array(
- 'global_styles' => $global_styles['font2'],
- 'expected_done' => array( 'font2-200-900-normal', 'font2-200-900-italic', 'font2' ),
- 'expected_output' => sprintf(
- '%s; %s \n',
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic']
- ),
- ),
- 'print font3' => array(
- 'global_styles' => $global_styles['font3'],
- 'expected_done' => array( 'font3', 'font3-bold-normal' ),
- 'expected_output' => sprintf(
- '%s \n',
- $font_faces['font3-bold-normal']
- ),
- ),
- 'print all fonts' => array(
- 'global_styles' => $global_styles['all'],
- 'expected_done' => array(
- 'font1-300-normal',
- 'font1-300-italic',
- 'font1-900-normal',
- 'font1',
- 'font2-200-900-normal',
- 'font2-200-900-italic',
- 'font2',
- 'font3-bold-normal',
- 'font3',
- ),
- 'expected_output' => sprintf(
- '%s; %s; %s; %s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal'],
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic'],
- $font_faces['font3-bold-normal']
- ),
- ),
- 'print all valid fonts' => array(
- 'global_styles' => $global_styles['all with invalid element'],
- 'expected_done' => array(
- 'font1-300-normal',
- 'font1-300-italic',
- 'font1-900-normal',
- 'font1',
- 'font2-200-900-normal',
- 'font2-200-900-italic',
- 'font2',
- 'font3-bold-normal',
- 'font3',
- ),
- 'expected_output' => sprintf(
- '%s; %s; %s; %s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal'],
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic'],
- $font_faces['font3-bold-normal']
- ),
- ),
- );
- }
-
- /**
- * Gets user-selected fonts for global styles for the mock provider.
- *
- * @since X.X.X
- *
- * @return array
- */
- protected function get_mock_user_selected_fonts_global_styles() {
- return array(
- 'font1' => array(
- 'elements' => array(
- 'heading' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'normal',
- 'fontWeight' => '300',
- ),
- ),
- 'caption' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'italic',
- 'fontWeight' => '300',
- ),
- ),
- ),
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'normal',
- 'fontWeight' => '900',
- ),
- ),
- 'font2' => array(
- 'elements' => array(
- 'heading' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font2',
- 'fontStyle' => 'normal',
- 'fontWeight' => '200-900',
- ),
- ),
- 'button' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font2',
- 'fontStyle' => 'italic',
- 'fontWeight' => '200-900',
- ),
- ),
- ),
- ),
- 'font3' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font3',
- 'fontStyle' => 'normal',
- 'fontWeight' => 'bold',
- ),
- ),
- 'all' => array(
- 'elements' => array(
- 'link' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'italic',
- 'fontWeight' => '300',
- ),
- ),
- 'heading' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'normal',
- 'fontWeight' => '900',
- ),
- ),
- 'caption' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'italic',
- 'fontWeight' => '300',
- ),
- ),
- 'button' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font2',
- 'fontStyle' => 'normal',
- 'fontWeight' => '200-900',
- ),
- ),
- ),
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font3',
- 'fontStyle' => 'normal',
- 'fontWeight' => 'bold',
- ),
- ),
- 'all with invalid element' => array(
- 'elements' => array(
- 'link' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'italic',
- 'fontWeight' => '300',
- ),
- ),
- 'heading' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'normal',
- 'fontWeight' => '900',
- ),
- ),
- 'caption' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'italic',
- 'fontWeight' => '300',
- ),
- ),
- 'button' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font2',
- 'fontStyle' => 'normal',
- 'fontWeight' => '200-900',
- ),
- ),
- 'invalid' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font2',
- 'fontStyle' => 'italic',
- 'fontWeight' => '200-900',
- ),
- ),
- ),
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font3',
- 'fontStyle' => 'normal',
- 'fontWeight' => 'bold',
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpDeregisterFontFamily.php b/phpunit/tests/fonts-api/wpDeregisterFontFamily.php
deleted file mode 100644
index 59db2e2abc8264..00000000000000
--- a/phpunit/tests/fonts-api/wpDeregisterFontFamily.php
+++ /dev/null
@@ -1,74 +0,0 @@
-set_up_mock( 'remove_font_family' );
- $mock->expects( $this->once() )
- ->method( 'remove_font_family' )
- ->with(
- $this->identicalTo( $font_family_handle )
- );
-
- wp_deregister_font_family( $font_family_handle );
- }
-
- /**
- * Integration test for enqueuing before registering a font family and all of its variations.
- *
- * @dataProvider data_font_family_handles
- *
- * @param string $font_family Font family to test.
- */
- public function test_should_deregister_before_registration( $font_family ) {
- wp_deregister_font_family( $font_family );
-
- $this->assertIsArray( $this->get_registered(), 'Registration queue should be an array' );
- $this->assertEmpty( $this->get_registered(), 'Registration queue should be empty after deregistering' );
- }
-
- /**
- * Integration test for deregistering a font family and all of its variations.
- *
- * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations
- *
- * @param string $font_family Font family to test.
- * @param array $inputs Font family(ies) and variations to pre-register.
- * @param array $registered_handles Expected handles after registering.
- * @param array $expected Array of expected handles.
- */
- public function test_deregister_after_registration( $font_family, array $inputs, array $registered_handles, array $expected ) {
- foreach ( $inputs as $handle => $variations ) {
- $this->setup_register( $handle, $variations );
- }
- // Test the before state, just to make sure.
- $this->assertSame( $registered_handles, $this->get_registered_handles(), 'Font family and variations should be registered before deregistering' );
-
- wp_deregister_font_family( $font_family );
-
- // Test after deregistering.
- $this->assertIsArray( $this->get_registered_handles(), 'Registration queue should be an array' );
- $this->assertSame( $expected, $this->get_registered_handles(), 'Registration queue should match after deregistering' );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpDeregisterFontVariation.php b/phpunit/tests/fonts-api/wpDeregisterFontVariation.php
deleted file mode 100644
index 78b4bf51f758af..00000000000000
--- a/phpunit/tests/fonts-api/wpDeregisterFontVariation.php
+++ /dev/null
@@ -1,298 +0,0 @@
-wp_fonts = wp_fonts();
- $this->fonts_to_register = $this->get_registered_local_fonts();
- }
-
- /**
- * Sets up the unit test by mocking the WP_Dependencies object using stdClass and
- * registering each font family directly to the WP_Webfonts::$registered property
- * and its variations to the mocked $deps property.
- */
- private function setup_unit_test() {
- $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_fonts );
- }
-
- /**
- * Sets up the integration test by properly registering each font family and its variations
- * by using the WP_Webfonts::add() and WP_Webfonts::add_variation() methods.
- */
- private function setup_integration_test() {
- foreach ( $this->fonts_to_register as $font_family_handle => $variations ) {
- $this->setup_register( $font_family_handle, $variations, $this->wp_fonts );
- }
- }
-
- /**
- * Testing the test setup to ensure it works.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_mocked_setup( $font_family_handle, $variation_handle ) {
- $this->setup_unit_test();
-
- $this->assertArrayHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be in the registered queue before removal' );
- $this->assertContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' );
- }
-
- /**
- * Unit test for deregistering a font-family's variation using mock of WP_Webfonts.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family to test.
- * @param string $variation_handle Variation's handle to test.
- */
- public function test_should_deregister_when_mocked( $font_family_handle, $variation_handle ) {
- $mock = $this->set_up_mock( 'remove_variation' );
- $mock->expects( $this->once() )
- ->method( 'remove_variation' )
- ->with(
- $this->identicalTo( $font_family_handle, $variation_handle )
- );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_should_do_nothing
- *
- * @param string $font_family Font family name.
- * @param string $font_family_handle Font family handle.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) {
- // Set up the test.
- unset( $this->fonts_to_register[ $font_family ] );
- $this->setup_unit_test();
- $registered_queue = $this->wp_fonts->registered;
-
- // Run the tests.
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertSame( $registered_queue, $this->wp_fonts->registered, 'Registered queue should not have changed' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_should_do_nothing
- *
- * @param string $font_family Font family name.
- * @param string $font_family_handle Font family handle.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) {
- // Set up the test.
- unset( $this->fonts_to_register[ $font_family ] );
- $this->setup_integration_test();
- $registered_queue = $this->wp_fonts->get_registered();
-
- // Run the tests.
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertSameSets( $registered_queue, $this->wp_fonts->get_registered(), 'Registered queue should not have changed' );
- }
-
- /**
- * Data provider for testing removal of variations.
- *
- * @return array
- */
- public function data_should_do_nothing() {
- return array(
- 'Font with 1 variation' => array(
- 'font_family' => 'merriweather',
- 'font_family_handle' => 'merriweather',
- 'variation_handle' => 'merriweather-200-900-normal',
- ),
- 'Font with multiple variations' => array(
- 'font_family' => 'Source Serif Pro',
- 'font_family_handle' => 'source-serif-pro',
- 'variation_handle' => 'Source Serif Pro-300-normal',
- ),
- );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
- $this->setup_remove_variation_from_registered( $variation_handle );
-
- // Run the tests.
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
- $this->setup_remove_variation_from_registered( $variation_handle );
-
- // Run the tests.
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
- $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
-
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
- $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
-
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
-
- $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
-
- $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' );
-
- wp_deregister_font_variation( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Remove the variation handle from the font family's deps.
- *
- * @param string $font_family_handle Font family.
- * @param string $variation_handle The variation handle to remove.
- */
- private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) {
- foreach ( $this->wp_fonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) {
- if ( $variation_handle !== $vhandle ) {
- continue;
- }
- unset( $this->wp_fonts->registered[ $font_family_handle ]->deps[ $index ] );
- break;
- }
- }
-
- /**
- * Removes the variation from the WP_Webfonts::$registered queue.
- *
- * @param string $variation_handle The variation handle to remove.
- */
- private function setup_remove_variation_from_registered( $variation_handle ) {
- unset( $this->wp_fonts->registered[ $variation_handle ] );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpEnqueueFontVariations.php b/phpunit/tests/fonts-api/wpEnqueueFontVariations.php
deleted file mode 100644
index 6e026d99f58897..00000000000000
--- a/phpunit/tests/fonts-api/wpEnqueueFontVariations.php
+++ /dev/null
@@ -1,82 +0,0 @@
-set_up_mock( 'enqueue' );
- $mock->expects( $this->once() )
- ->method( 'enqueue' )
- ->with(
- $this->identicalTo( $handles )
- );
-
- wp_enqueue_font_variations( $handles );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_variation_handles() {
- return array(
- '1 variation handle' => array( 'merriweather-200-900-normal' ),
- 'multiple same font family handles' => array( array( 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ) ),
- 'handles from different font families' => array( array( 'merriweather-200-900-normal', 'Source Serif Pro-900-italic' ) ),
- );
- }
-
- /**
- * Integration test for enqueuing one or more specific variations.
- *
- * @dataProvider data_enqueue_variations
- *
- * @param string|string[] $handles Variation handles to test.
- * @param array $expected Expected queue.
- */
- public function test_should_enqueue_after_registration( $handles, array $expected ) {
- foreach ( $this->get_data_registry() as $handle => $variations ) {
- $this->setup_register( $handle, $variations );
- }
-
- wp_enqueue_font_variations( $handles );
- $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' );
- $this->assertSame( $expected, $this->get_enqueued_handles(), 'Queue should contain the given handles' );
- }
-
- /**
- * Integration test for enqueuing before registering one or more specific variations.
- *
- * @dataProvider data_enqueue_variations
- *
- * @param string|string[] $handles Variation handles to test.
- * @param array $not_used Not used.
- * @param array $expected Expected "queued_before_register" queue.
- */
- public function test_should_enqueue_before_registration( $handles, array $not_used, array $expected ) {
- wp_enqueue_font_variations( $handles );
-
- $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given handles' );
- $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpEnqueueFonts.php b/phpunit/tests/fonts-api/wpEnqueueFonts.php
deleted file mode 100644
index c00fb0f2dfdae5..00000000000000
--- a/phpunit/tests/fonts-api/wpEnqueueFonts.php
+++ /dev/null
@@ -1,122 +0,0 @@
-set_up_mock( 'enqueue' );
- $mock->expects( $this->once() )
- ->method( 'enqueue' )
- ->with(
- $this->identicalTo( $expected_handles )
- );
-
- wp_enqueue_fonts( $font_families );
- }
-
- /**
- * Integration test for enqueuing a font family and all of its variations.
- *
- * @dataProvider data_should_enqueue
- *
- * @param string[] $font_families Font families to test.
- * @param string[] $expected_handles Expected handles passed to WP_Fonts::enqueue().
- */
- public function test_should_enqueue_after_registration( $font_families, $expected_handles ) {
- // Register the font-families.
- foreach ( $this->get_data_registry() as $handle => $variations ) {
- $this->setup_register( $handle, $variations );
- }
-
- wp_enqueue_fonts( $font_families );
-
- $this->assertEmpty( $this->get_queued_before_register(), '"queued_before_register" queue should be empty' );
- $this->assertSame( $expected_handles, $this->get_enqueued_handles(), 'Queue should contain the given font family(ies)' );
- }
-
- /**
- * Integration test for enqueuing before registering a font family and all of its variations.
- *
- * @dataProvider data_should_enqueue
- *
- * @param string[] $font_families Font families to test.
- * @param string[] $expected_handles Expected handles passed to WP_Fonts::enqueue().
- */
- public function test_should_enqueue_before_registration( $font_families, $expected_handles ) {
- wp_enqueue_fonts( $font_families );
-
- // Set up what "queued_before_register" queue should be.
- $expected = array();
- foreach ( $expected_handles as $handle ) {
- $expected[ $handle ] = null;
- }
- $this->assertSame( $expected, $this->get_queued_before_register(), '"queued_before_register" queue should contain the given font family(ies)' );
- $this->assertEmpty( $this->get_enqueued_handles(), 'Queue should be empty' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_enqueue() {
- return array(
- '1: single word handle' => array(
- 'font_families' => array( 'lato' ),
- 'expected_handles' => array( 'lato' ),
- ),
- '1: multiple word handle' => array(
- 'font_families' => array( 'source-serif-pro' ),
- 'expected_handles' => array( 'source-serif-pro' ),
- ),
- '1: single word name' => array(
- 'font_families' => array( 'Merriweather' ),
- 'expected_handles' => array( 'merriweather' ),
- ),
- '1: multiple word name' => array(
- 'font_families' => array( 'My Font' ),
- 'expected_handles' => array( 'my-font' ),
- ),
- '>1: single word handle' => array(
- 'font_families' => array( 'lato', 'merriweather' ),
- 'expected_handles' => array( 'lato', 'merriweather' ),
- ),
- '>1: multiple word handle' => array(
- 'font_families' => array( 'source-serif-pro', 'my-font' ),
- 'expected_handles' => array( 'source-serif-pro', 'my-font' ),
- ),
- '>1: single word name' => array(
- 'font_families' => array( 'Lato', 'Merriweather' ),
- 'expected_handles' => array( 'lato', 'merriweather' ),
- ),
- '>1: multiple word name' => array(
- 'font_families' => array( 'My Font', 'Source Serif Pro' ),
- 'expected_handles' => array( 'my-font', 'source-serif-pro' ),
- ),
- '>1: mixture of word handles and names' => array(
- 'font_families' => array( 'Source Serif Pro', 'Merriweather', 'my-font', 'Lato' ),
- 'expected_handles' => array( 'source-serif-pro', 'merriweather', 'my-font', 'lato' ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts.php b/phpunit/tests/fonts-api/wpFonts.php
deleted file mode 100644
index 3b33e58edf2995..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts.php
+++ /dev/null
@@ -1,38 +0,0 @@
-assertInstanceOf( WP_Fonts::class, wp_fonts() );
- }
-
- public function test_global_set() {
- global $wp_fonts;
- $this->assertNull( $wp_fonts );
- $instance = wp_fonts();
- $this->assertInstanceOf( WP_Fonts::class, $wp_fonts );
- $this->assertSame( $instance, $wp_fonts );
- }
-
- public function test_local_provider_is_automatically_registered() {
- $expected = array(
- 'local' => array(
- 'class' => 'WP_Fonts_Provider_Local',
- 'fonts' => array(),
- ),
- );
- $this->assertSame( $expected, wp_fonts()->get_providers() );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/add.php b/phpunit/tests/fonts-api/wpFonts/add.php
deleted file mode 100644
index 85d3d98c0f3556..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/add.php
+++ /dev/null
@@ -1,44 +0,0 @@
-assertTrue( $wp_fonts->add( $handle, false ), 'Registering a handle should return true' );
- $this->assertCount( 1, $wp_fonts->registered );
- $this->assertArrayHasKey( $handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_handles() {
- return array(
- 'name: multiple' => array( 'Source Serif Pro' ),
- 'handle: multiple' => array( 'source-serif-pro' ),
- 'name: single' => array( 'Merriweather' ),
- 'handle: single' => array( 'merriweather' ),
- 'handle: variation' => array( 'my-custom-font-200-900-normal' ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php b/phpunit/tests/fonts-api/wpFonts/addFontFamily.php
deleted file mode 100644
index 69b6bccf5ae352..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php
+++ /dev/null
@@ -1,65 +0,0 @@
-add_font_family( $font_family );
-
- $this->assertSame( $expected, $font_family_handle, 'Registering a font-family should return its handle' );
- $this->assertCount( 1, $wp_fonts->registered );
- $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_handles() {
- return array(
- 'name: multiple' => array(
- 'font_family' => 'Source Serif Pro',
- 'expected' => 'source-serif-pro',
- ),
- 'handle: multiple' => array(
- 'font_family' => 'source-serif-pro',
- 'expected' => 'source-serif-pro',
- ),
- 'name: single' => array(
- 'font_family' => 'Merriweather',
- 'expected' => 'merriweather',
- ),
- 'handle: single' => array(
- 'font_family' => 'merriweather',
- 'expected' => 'merriweather',
- ),
- 'handle: variation' => array(
- 'font_family' => 'my-custom-font-200-900-normal',
- 'expected' => 'my-custom-font-200-900-normal',
- ),
- 'name: multiple font-families' => array(
- 'font_family' => 'Source Serif Pro, Merriweather',
- 'expected' => 'source-serif-pro',
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/addVariation.php b/phpunit/tests/fonts-api/wpFonts/addVariation.php
deleted file mode 100644
index 5b6d93bcc99fe0..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/addVariation.php
+++ /dev/null
@@ -1,149 +0,0 @@
-add( $font_family_handle, false );
-
- $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle );
- $this->assertSame( $expected, $variation_handle, 'Registering a variation should return its handle' );
- $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' );
- $this->assertSame( array( $expected ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should be registered to font family' );
- }
-
- /**
- * @dataProvider data_valid_variation
- *
- * @param string|bool $expected Expected results.
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- * @param string $variation_handle Optional. The variation's handle.
- */
- public function test_should_not_reregister_font_family( $expected, $font_family_handle, array $variation, $variation_handle = '' ) {
- $wp_fonts = new WP_Fonts();
- $wp_fonts->add( $font_family_handle, false );
-
- $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle );
-
- // Font family should appear only once in the registered queue.
- $expected = array( $font_family_handle, $variation_handle );
- $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Font family should not be re-registered after registering a variation' );
- }
-
- /**
- * @dataProvider data_valid_variation
- *
- * @param string|bool $expected Expected results.
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- * @param string $variation_handle Optional. The variation's handle.
- */
- public function test_should_not_reregister_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) {
- $wp_fonts = new WP_Fonts();
- $wp_fonts->add( $font_family_handle, false );
-
- // Set up the test.
- $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle );
-
- // Run the test.
- $variant_handle_on_reregister = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle );
- $this->assertSame( $expected, $variant_handle_on_reregister, 'Variation should be registered to font family' );
- $this->assertSame( $variation_handle, $variant_handle_on_reregister, 'Variation should return the previously registered variant handle' );
- $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should only be registered once' );
-
- $this->assertCount( 2, $wp_fonts->registered );
- $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' );
- }
-
- /**
- * @dataProvider data_valid_variation
- *
- * @param string|bool $expected Expected results.
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- * @param string $variation_handle Optional. The variation's handle.
- */
- public function test_should_register_font_family_and_variation( $expected, $font_family_handle, array $variation, $variation_handle = '' ) {
- $wp_fonts = new WP_Fonts();
-
- $variation_handle = $wp_fonts->add_variation( $font_family_handle, $variation, $variation_handle );
- $this->assertSame( $expected, $variation_handle, 'Variation should return its registered handle' );
-
- // Extra checks to ensure both are registered.
- $this->assertCount( 2, $wp_fonts->registered );
- $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' );
- $this->assertArrayHasKey( $variation_handle, $wp_fonts->registered, 'Variation handle should be in the registry after registration' );
- $this->assertSame( array( $variation_handle ), $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should be registered to the font family' );
- }
-
- /**
- * @dataProvider data_font_family_handle_undefined
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- */
- public function test_should_not_register_font_family_or_variant( $font_family_handle, array $variation ) {
- $this->expectNotice();
- $this->expectNoticeMessage( 'Font family handle must be a non-empty string.' );
-
- $wp_fonts = new WP_Fonts();
- $wp_fonts->add_variation( $font_family_handle, $variation );
-
- $this->assertEmpty( $wp_fonts->registered, 'Registered queue should be empty' );
- $this->assertEmpty( $this->get_variations( $font_family_handle, $wp_fonts ), 'Variation should not be registered to the font family' );
- }
-
- /**
- * @dataProvider data_font_family_undefined_in_variation
- * @dataProviders data_unable_determine_variation_handle
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- * @param string $expected_message Expected notice message.
- */
- public function test_should_not_register_variation_when_font_family_not_defined( $font_family_handle, array $variation, $expected_message ) {
- $this->expectNotice();
- $this->expectNoticeMessage( $expected_message );
-
- $wp_fonts = new WP_Fonts();
- $this->assertNull( $wp_fonts->add_variation( $font_family_handle, $variation ) );
- }
-
- /**
- * @dataProvider data_unable_determine_variation_handle
- *
- * @param string $font_family_handle The font family's handle for this variation.
- * @param array $variation An array of variation properties to add.
- */
- public function test_should_register_font_family_when_variant_fails_to_register( $font_family_handle, array $variation ) {
- $this->expectNotice();
- $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' );
-
- $wp_fonts = new WP_Fonts();
- $wp_fonts->add_variation( $font_family_handle, $variation );
-
- $this->assertCount( 1, $wp_fonts->registered );
- $this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/dequeue.php b/phpunit/tests/fonts-api/wpFonts/dequeue.php
deleted file mode 100644
index 6cd11dc55b9376..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/dequeue.php
+++ /dev/null
@@ -1,72 +0,0 @@
-dequeue( $handles );
- $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue should be empty' );
- $this->assertEmpty( $wp_fonts->queue, 'Queue should be empty' );
- }
-
- /**
- * Integration test for dequeuing from queue. It first registers and then enqueues before dequeuing.
- *
- * @dataProvider data_enqueue
- * @dataProvider data_enqueue_variations
- *
- * @param string|string[] $handles Handles to test.
- */
- public function test_should_dequeue_from_queue( $handles ) {
- $wp_fonts = new WP_Fonts();
-
- // Register and enqueue.
- foreach ( $this->get_data_registry() as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $wp_fonts );
- }
- $wp_fonts->enqueue( $handles );
-
- // To make sure the handles are in the queue before dequeuing.
- $this->assertNotEmpty( $wp_fonts->queue, 'Queue not be empty before dequeueing' );
-
- // Run the test.
- $wp_fonts->dequeue( $handles );
- $this->assertEmpty( $wp_fonts->queue, 'Queue should be empty after dequeueing' );
- }
-
- /**
- * Integration test for dequeuing from prequeue. It enqueues first.
- *
- * @dataProvider data_enqueue
- * @dataProvider data_enqueue_variations
- *
- * @param string|string[] $handles Handles to test.
- */
- public function test_should_dequeue_from_prequeue( $handles ) {
- $wp_fonts = new WP_Fonts();
- $wp_fonts->enqueue( $handles );
- $this->assertNotEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue not be empty before dequeueing' );
-
- $wp_fonts->dequeue( $handles );
- $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), 'Prequeue should be empty after dequeueing' );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/doItem.php b/phpunit/tests/fonts-api/wpFonts/doItem.php
deleted file mode 100644
index a92bd244d1f2ae..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/doItem.php
+++ /dev/null
@@ -1,336 +0,0 @@
-wp_fonts = new WP_Fonts();
- }
-
- public function test_should_return_false_when_provider_not_registered() {
- $this->assertFalse( $this->wp_fonts->do_item( 'provider_not_registered' ) );
- }
-
- /**
- * @dataProvider data_provider_definitions
- *
- * @param array $provider Provider to mock.
- */
- public function test_should_return_false_when_no_fonts_enqueued_for_provider( array $provider ) {
- $this->setup_provider_property_mock( $this->wp_fonts, $provider );
- $this->assertFalse( $this->wp_fonts->do_item( $provider['id'] ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_provider_definitions() {
- $providers = $this->get_provider_definitions();
-
- return array(
- 'mock' => array( $providers['mock'] ),
- 'local' => array( $providers['local'] ),
- );
- }
-
- /**
- * Test the test set up to ensure the `Tests_Fonts_WpFonts_DoItem_::setup_provider_property_mock()`
- * method works as expected.
- */
- public function test_mocking_providers_property() {
- $font_handles = array( 'font1', 'font2', 'font3' );
- $expected = array(
- 'mock' => array(
- 'class' => Mock_Provider::class,
- 'fonts' => $font_handles,
- ),
- );
-
- $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles );
- $actual = $this->property['WP_Fonts::$providers']->getValue( $this->wp_fonts );
- $this->assertSame( $expected, $actual );
- }
-
- /**
- * Test the private method WP_Fonts::get_enqueued_fonts_for_provider().
- *
- * Why? This test validates the right fonts are returned for use within
- * WP_Fonts::do_item().
- *
- * @dataProvider data_get_enqueued_fonts_for_provider
- *
- * @param array $font_handles Array of handles for the provider.
- * @param array $to_do Handles to set for the WP_Fonts::$to_do property.
- * @param array $expected Expected result.
- */
- public function test_get_enqueued_fonts_for_provider( $font_handles, $to_do, $expected ) {
- // Set up the `to_do` property.
- $this->wp_fonts->to_do = $to_do;
-
- // Open the method's visibility for testing.
- $get_enqueued_fonts_for_provider = $this->get_reflection_method( 'get_enqueued_fonts_for_provider' );
-
- // Mock the WP_Fonts::$property to set up the test.
- $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles );
-
- $actual = $get_enqueued_fonts_for_provider->invoke( $this->wp_fonts, 'mock' );
- $this->assertSameSets( $expected, $actual );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_get_enqueued_fonts_for_provider() {
- return array(
- 'to_do queue is empty' => array(
- 'font_handles ' => array( 'font1', 'font2', 'font3' ),
- 'to_do' => array(),
- 'expected' => array(),
- ),
- 'fonts not in to_do queue' => array(
- 'font_handles ' => array( 'font1', 'font2', 'font3' ),
- 'to_do' => array( 'font12', 'font13' ),
- 'expected' => array(),
- ),
- '2 of the provider fonts in to_do queue' => array(
- 'font_handles ' => array( 'font11', 'font12', 'font13' ),
- 'to_do' => array( 'font11', 'font13' ),
- 'expected' => array( 'font11', 'font13' ),
- ),
- 'do all of the provider fonts' => array(
- 'font_handles ' => array( 'font21', 'font22', 'font23' ),
- 'to_do' => array( 'font21', 'font22', 'font23' ),
- 'expected' => array( 'font21', 'font22', 'font23' ),
- ),
- );
- }
-
- /**
- * Test the private method WP_Fonts::get_font_properties_for_provider().
- *
- * Why? This test validates the right font properties are returned for use within
- * WP_Fonts::do_item().
- *
- * @dataProvider data_get_font_properties_for_provider
- *
- * @param array $font_handles Web fonts for testing.
- * @param array $expected Expected result.
- */
- public function test_get_font_properties_for_provider( $font_handles, $expected ) {
- // Set up the fonts for WP_Dependencies:get_data().
- $fonts = $this->get_registered_fonts();
- // Set all variations to 'mock' provider.
-
- // Mock the WP_Fonts::$property to set up the test.
- $this->setup_provider_property_mock( $this->wp_fonts, $this->get_provider_definitions( 'mock' ), $font_handles );
- $this->setup_registration_mocks( $fonts, $this->wp_fonts );
-
- // Open the method's visibility for testing.
- $method = $this->get_reflection_method( 'get_font_properties_for_provider' );
-
- $actual = $method->invoke( $this->wp_fonts, $font_handles );
- $this->assertSame( $expected, $actual );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_get_font_properties_for_provider() {
- $fonts = $this->get_registered_fonts();
-
- return array(
- 'handles not registered' => array(
- 'font_handles' => array( 'font-not-registered1', 'font-not-registered2', 'font-not-registered3' ),
- 'expected' => array(),
- ),
- 'registered and non-registered handles' => array(
- 'font_handles' => array( 'Source Serif Pro-300-normal', 'not-registered-handle', 'Source Serif Pro-900-italic' ),
- 'expected' => array(
- 'Source Serif Pro-300-normal' => $fonts['Source Serif Pro']['Source Serif Pro-300-normal'],
- 'Source Serif Pro-900-italic' => $fonts['Source Serif Pro']['Source Serif Pro-900-italic'],
- ),
- ),
- 'font-family handles, ie no "font-properties" extra data' => array(
- 'font_handles' => array( 'font1', 'font2', 'merriweather' ),
- 'expected' => array(),
- ),
- );
- }
-
- /**
- * @dataProvider data_print_enqueued_fonts
- *
- * @param array $provider Define provider.
- * @param array $fonts Fonts to register and enqueue.
- * @param array $expected Expected results.
- */
- public function test_should_trigger_provider_when_mocked( array $provider, array $fonts, array $expected ) {
- $this->setup_print_deps( $provider, $fonts );
-
- $provider_mock = $this->setup_object_mock( array( 'set_fonts', 'print_styles' ), $provider['class'] );
-
- // Test the provider's methods are invoked.
- $provider_mock->expects( $this->once() )->method( 'set_fonts' )->with( $this->identicalTo( $expected['set_fonts'] ) );
- $provider_mock->expects( $this->once() )->method( 'print_styles' );
-
- // Set up the WP_Fonts::$provider_instances property.
- $provider_instances = $this->get_reflection_property( 'provider_instances' );
- $provider_instances->setValue( $this->wp_fonts, array( $provider['id'] => $provider_mock ) );
-
- // Test the method successfully processes the provider.
- $this->expectOutputString( '' );
- $this->assertTrue( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return true' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_print_enqueued_fonts
- *
- * @param array $provider Define provider.
- * @param array $fonts Fonts to register and enqueue.
- * @param array $expected Expected results.
- */
- public function test_should_print( array $provider, array $fonts, array $expected ) {
- $this->setup_print_deps( $provider, $fonts );
-
- // Test the method successfully processes the provider.
- $this->expectOutputString( $expected['printed_output'] );
- $this->assertTrue( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return true' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_print_enqueued_fonts() {
- $mock = $this->get_registered_mock_fonts();
- $local = $this->get_registered_local_fonts();
- $font_faces = $this->get_registered_fonts_css();
-
- return array(
- 'mock' => array(
- 'provider' => $this->get_provider_definitions( 'mock' ),
- 'fonts' => $mock,
- 'expected' => array(
- 'set_fonts' => array_merge( $mock['font1'], $mock['font2'], $mock['font3'] ),
- 'printed_output' => sprintf(
- '%s; %s; %s; %s; %s; %s \n',
- $font_faces['font1-300-normal'],
- $font_faces['font1-300-italic'],
- $font_faces['font1-900-normal'],
- $font_faces['font2-200-900-normal'],
- $font_faces['font2-200-900-italic'],
- $font_faces['font3-bold-normal']
- ),
- ),
- ),
- 'local' => array(
- 'provider' => $this->get_provider_definitions( 'local' ),
- 'fonts' => $local,
- 'expected' => array(
- 'set_fonts' => array_merge( $local['merriweather'], $local['Source Serif Pro'] ),
- 'printed_output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
- ),
- );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_not_print_enqueued_fonts
- *
- * @param array $provider Define provider.
- * @param array $fonts Fonts to register and enqueue.
- * @param array $expected Not used.
- * @param array $to_do_queue Value to set in the WP_Fonts::$to_do queue.
- */
- public function test_should_not_print_when_to_do_queue_empty( array $provider, array $fonts, $expected, $to_do_queue ) {
- $this->setup_print_deps( $provider, $fonts, $to_do_queue );
-
- // Test the method successfully processes the provider.
- $this->expectOutputString( '' );
- $this->assertFalse( $this->wp_fonts->do_item( $provider['id'] ), 'WP_Fonts::do_item() should return false' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_not_print_enqueued_fonts() {
- $mock = $this->get_registered_mock_fonts();
- $local = $this->get_registered_local_fonts();
-
- return array(
- 'mock provider when to_do queue is empty' => array(
- 'provider' => $this->get_provider_definitions( 'mock' ),
- 'fonts' => $mock,
- 'expected' => array(),
- 'to_do_queue' => array(),
- ),
- 'local provider when to_do queue is empty' => array(
- 'provider' => $this->get_provider_definitions( 'local' ),
- 'fonts' => $local,
- 'expected' => array(),
- 'to_do_queue' => array(),
- ),
- 'fonts not in to_do queue' => array(
- 'provider' => $this->get_provider_definitions( 'mock' ),
- 'fonts' => $mock,
- 'expected' => array(),
- 'to_do_queue' => array(),
- ),
- );
- }
-
- /**
- * Sets up the print dependencies.
- *
- * @param array $provider Provider id and class.
- * @param array $fonts Fonts to register and enqueue.
- * @param array|null $to_do_queue Set the WP_Fonts:$to_do queue.
- */
- private function setup_print_deps( $provider, $fonts, $to_do_queue = null ) {
- // Set up the fonts for WP_Dependencies:get_data().
- $mocks = $this->setup_registration_mocks( $fonts, $this->wp_fonts );
- $handles = array_keys( $mocks );
- $this->setup_provider_property_mock( $this->wp_fonts, $provider, $handles );
-
- // Set up the `WP_Fonts::$to_do` and `WP_Fonts::$to_do_keyed_handles` properties.
- if ( null === $to_do_queue ) {
- $to_do_queue = $handles;
- }
-
- $this->wp_fonts->to_do = $to_do_queue;
- $to_do_keyed_handles = $this->get_reflection_property( 'to_do_keyed_handles' );
- $to_do_keyed_handles->setValue( $this->wp_fonts, array_flip( $to_do_queue ) );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/doItems.php b/phpunit/tests/fonts-api/wpFonts/doItems.php
deleted file mode 100644
index 8a29e7463406a8..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/doItems.php
+++ /dev/null
@@ -1,196 +0,0 @@
-wp_fonts = new WP_Fonts();
- }
-
- public function test_should_not_process_when_no_providers_registered() {
- $this->setup_deps( array( 'enqueued' => 'font1' ) );
-
- $done = $this->wp_fonts->do_items();
-
- $this->assertSame( array(), $done, 'WP_Fonts::do_items() should return an empty array' );
- $this->assertSame( array(), $this->wp_fonts->to_do, 'WP_Fonts::$to_do should be an empty array' );
- }
-
- /**
- * @dataProvider data_invalid_handles
- *
- * @param mixed $handles Handles to test.
- */
- public function test_should_throw_notice_when_invalid_handles( $handles ) {
- $this->expectNotice();
- $this->expectNoticeMessage( 'Handles must be a non-empty string or array of non-empty strings' );
-
- $this->wp_fonts->do_items( $handles );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_invalid_handles() {
- return array(
- 'null' => array( null ),
- 'empty array' => array( array() ),
- 'empty string' => array( '' ),
- 'array of empty strings' => array( array( '', '' ) ),
- 'array of mixed falsey values' => array( array( '', false, null, array() ) ),
- );
- }
-
- public function test_should_throw_notice_when_provider_class_not_found() {
- $this->expectNotice();
- $this->expectNoticeMessage( 'Class "Provider_Does_Not_Exist" not found for "doesnotexist" font provider' );
-
- $setup = array(
- 'provider' => array(
- 'doesnotexist' => array(
- 'id' => 'doesnotexist',
- 'class' => 'Provider_Does_Not_Exist',
- ),
- ),
- 'provider_handles' => array( 'doesnotexist' => array( 'font1' ) ),
- 'registered' => array(
- 'doesnotexist' => array(
- 'font1' => array(
- 'font1-300-normal' => array(
- 'provider' => 'doesnotexist',
- 'font-weight' => '300',
- 'font-style' => 'normal',
- 'font-display' => 'fallback',
- ),
- ),
- ),
- ),
- 'enqueued' => array( 'font1', 'font1-300-normal' ),
- );
- $this->setup_deps( $setup );
-
- $this->wp_fonts->do_items();
- }
-
- /**
- * @dataProvider data_print_enqueued
- *
- * @param array $setup Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_mocked_enqueued( $setup, $expected_done, $expected_output ) {
- $this->setup_deps( $setup );
-
- $this->expectOutputString( $expected_output );
- $actual_done = $this->wp_fonts->do_items();
- $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
- }
-
- /**
- * Integration test that registers providers and fonts and then enqueues before
- * testing the printing functionality.
- *
- * @dataProvider data_print_enqueued
- *
- * @param array $setup Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) {
- $this->setup_integrated_deps( $setup );
-
- $this->expectOutputString( $expected_output, 'Printed @font-face styles should match' );
- $actual_done = $this->wp_fonts->do_items();
- $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
- }
-
- /**
- * Integration test to validate printing given handles. Rather than mocking internal functionality,
- * it registers providers and fonts but does not enqueue.
- *
- * @dataProvider data_print_enqueued
- *
- * @param array $setup Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) {
- $this->setup_integrated_deps( $setup, false );
- // Do not enqueue. Instead, pass the handles to WP_Fonts::do_items().
- $handles = $setup['enqueued'];
- $this->assertEmpty( $this->wp_fonts->queue, 'No fonts should be enqueued' );
-
- $this->expectOutputString( $expected_output );
- $actual_done = $this->wp_fonts->do_items( $handles );
- $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
- }
-
- /**
- * Sets up the dependencies for the mocked test.
- *
- * @param array $setup Dependencies to set up.
- */
- private function setup_deps( array $setup ) {
- $setup = array_merge(
- array(
- 'provider' => array(),
- 'provider_handles' => array(),
- 'registered' => array(),
- 'enqueued' => array(),
- ),
- $setup
- );
-
- if ( ! empty( $setup['provider'] ) ) {
- foreach ( $setup['provider'] as $provider_id => $provider ) {
- $this->setup_provider_property_mock( $this->wp_fonts, $provider, $setup['provider_handles'][ $provider_id ] );
- }
- }
-
- if ( ! empty( $setup['registered'] ) ) {
- $this->setup_registration_mocks( $setup['registered'], $this->wp_fonts );
- }
-
- if ( ! empty( $setup['enqueued'] ) ) {
- $queue = $this->get_reflection_property( 'queue' );
- $queue->setValue( $this->wp_fonts, $setup['enqueued'] );
- }
- }
-
- /**
- * Sets up the dependencies for integration test.
- *
- * @param array $setup Dependencies to set up.
- * @param bool $enqueue Whether to enqueue. Default true.
- */
- private function setup_integrated_deps( array $setup, $enqueue = true ) {
- foreach ( $setup['provider'] as $provider ) {
- $this->wp_fonts->register_provider( $provider['id'], $provider['class'] );
- }
- foreach ( $setup['registered'] as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $this->wp_fonts );
- }
-
- if ( $enqueue ) {
- $this->wp_fonts->enqueue( $setup['enqueued'] );
- }
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/enqueue.php b/phpunit/tests/fonts-api/wpFonts/enqueue.php
deleted file mode 100644
index 09c14676c7ae09..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/enqueue.php
+++ /dev/null
@@ -1,53 +0,0 @@
-enqueue( $handles );
-
- $this->assertSame( $expected, $this->get_queued_before_register( $wp_fonts ), 'Handles should be added to before registered queue' );
- $this->assertEmpty( $wp_fonts->queue, 'Handles should not be added to the enqueue queue when not registered' );
- }
-
- /**
- * Integration test for enqueuing (a) a font family and all of its variations or (b) specific variations.
- *
- * @dataProvider data_enqueue
- * @dataProviders data_enqueue_variations
- *
- * @param string|string[] $handles Handles to test.
- * @param array $expected Expected queue.
- */
- public function test_should_enqueue_when_registered( $handles, array $expected ) {
- $wp_fonts = new WP_Fonts();
- foreach ( $this->get_data_registry() as $font_family => $variations ) {
- $this->setup_register( $font_family, $variations, $wp_fonts );
- }
-
- $wp_fonts->enqueue( $handles );
-
- $this->assertEmpty( $this->get_queued_before_register( $wp_fonts ), '"queued_before_register" queue should be empty' );
- $this->assertSame( $expected, $wp_fonts->queue, 'Queue should contain the given handles' );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/getEnqueued.php b/phpunit/tests/fonts-api/wpFonts/getEnqueued.php
deleted file mode 100644
index bacb8a31aa6500..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/getEnqueued.php
+++ /dev/null
@@ -1,57 +0,0 @@
-assertEmpty( $wp_fonts->get_enqueued() );
- }
-
- /**
- * Unit test for when font families are enqueued.
- *
- * @dataProvider data_enqueue
- *
- * @param string|string[] $not_used Not used.
- * @param array $expected Expected queue.
- */
- public function test_should_return_queue_when_property_has_font_families( $not_used, array $expected ) {
- $wp_fonts = new WP_Fonts();
- $wp_fonts->queue = $expected;
-
- $this->assertSame( $expected, $wp_fonts->get_enqueued() );
- }
-
- /**
- * Full integration test that registers and enqueues the queue
- * is properly wired for "get_enqueued()".
- *
- * @dataProvider data_enqueue
- *
- * @param string|string[] $font_family Font family to test.
- * @param array $expected Expected queue.
- */
- public function test_should_return_queue_when_font_families_registered_and_enqueued( $font_family, array $expected ) {
- $wp_fonts = new WP_Fonts();
-
- // Register and enqueue.
- foreach ( $this->get_data_registry() as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $wp_fonts );
- }
- $wp_fonts->enqueue( $font_family );
-
- $this->assertSame( $expected, $wp_fonts->get_enqueued() );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/getProviders.php b/phpunit/tests/fonts-api/wpFonts/getProviders.php
deleted file mode 100644
index 410adaace286aa..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/getProviders.php
+++ /dev/null
@@ -1,62 +0,0 @@
-wp_fonts = new WP_Fonts();
-
- $this->providers_property = new ReflectionProperty( WP_Fonts::class, 'providers' );
- $this->providers_property->setAccessible( true );
- }
-
- public function test_should_be_empty() {
- $actual = $this->wp_fonts->get_providers();
- $this->assertIsArray( $actual, 'Should return an empty array' );
- $this->assertEmpty( $actual, 'Should return an empty array when no providers are registered' );
- }
-
- /**
- * @dataProvider data_get_providers
- *
- * @param array $providers Array of providers to test.
- * @param array $expected Expected results.
- */
- public function test_get_providers( array $providers, array $expected ) {
- $this->setup_providers( $providers );
- $this->assertSame( $expected, $this->wp_fonts->get_providers() );
- }
-
- /**
- * Sets up the given providers and stores them in the `WP_Fonts::providers` property.
- *
- * @param array $providers Array of providers to set up.
- */
- private function setup_providers( array $providers ) {
- $data = array();
-
- foreach ( $providers as $provider_id => $class ) {
- $data[ $provider_id ] = array(
- 'class' => $class,
- 'fonts' => array(),
- );
- }
-
- $this->providers_property->setValue( $this->wp_fonts, $data );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/getRegistered.php b/phpunit/tests/fonts-api/wpFonts/getRegistered.php
deleted file mode 100644
index 7dc116f5875390..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/getRegistered.php
+++ /dev/null
@@ -1,90 +0,0 @@
-assertEmpty( $wp_fonts->get_registered() );
- }
-
- /**
- * Unit test for when font families are enqueued.
- *
- * @dataProvider data_get_registered
- *
- * @param array $inputs Font family(ies) and variations to register.
- */
- public function test_should_return_queue_when_mocking_registered_property( array $inputs ) {
- $wp_fonts = new WP_Fonts();
- $mocks = $this->setup_registration_mocks( $inputs, $wp_fonts );
- $expected = array_keys( $mocks );
-
- $this->assertSame( $expected, $wp_fonts->get_registered() );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_get_registered() {
- return array(
- 'no variations' => array(
- 'inputs' => array(
- 'lato' => array(),
- ),
- ),
- 'with 1 variation' => array(
- 'inputs' => array(
- 'Source Serif Pro' => array( 'variation-1' ),
- ),
- ),
- 'with 2 variations' => array(
- 'inputs' => array(
- 'my-cool-font' => array( 'cool-1', 'cool-2' ),
- ),
- ),
- 'when multiple font families registered' => array(
- 'inputs' => array(
- 'font-family-1' => array( 'variation-11', 'variation-12' ),
- 'font-family-2' => array( 'variation-21', 'variation-22' ),
- 'font-family-3' => array( 'variation-31', 'variation-32' ),
- ),
- ),
- );
- }
-
- /**
- * Full integration test that registers varying number of font families and variations
- * to validate if "get_registered()" internals is property wired to the registered queue.
- *
- * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations
- *
- * @param string $font_family Not used.
- * @param array $inputs Font family(ies) and variations to register.
- * @param array $expected Expected results.
- */
- public function test_should_return_queue_when_items_are_registered( $font_family, array $inputs, array $expected ) {
- $wp_fonts = new WP_Fonts();
-
- // Register before testing.
- foreach ( $inputs as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $wp_fonts );
- }
-
- $this->assertSame( $expected, $wp_fonts->get_registered() );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/query.php b/phpunit/tests/fonts-api/wpFonts/query.php
deleted file mode 100644
index 0e56b515e7ebf4..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/query.php
+++ /dev/null
@@ -1,151 +0,0 @@
-wp_fonts = new WP_Fonts();
- }
-
- /**
- * @dataProvider data_invalid_query
- * @dataProvider data_valid_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_should_fail_when_handles_not_registered( $query_handle ) {
- $this->assertFalse( $this->wp_fonts->query( $query_handle, 'registered' ) );
- }
-
- /**
- * @dataProvider data_invalid_query
- * @dataProvider data_valid_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_should_fail_when_handles_not_registered_or_enqueued( $query_handle ) {
- $this->assertFalse( $this->wp_fonts->query( $query_handle, 'queue' ) );
- }
-
- /**
- * @dataProvider data_valid_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_registered_query_should_succeed_when_registered( $query_handle ) {
- $this->setup_registry();
-
- $actual = $this->wp_fonts->query( $query_handle, 'registered' );
- $this->assertInstanceOf( '_WP_Dependency', $actual, 'Query should return an instance of _WP_Dependency' );
- $this->assertSame( $query_handle, $actual->handle, 'Query object handle should match the given handle to query' );
- }
-
- /**
- * @dataProvider data_valid_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_enqueued_query_should_succeed_when_registered_and_enqueued( $query_handle ) {
- $this->setup_registry();
- $this->wp_fonts->enqueue( $query_handle );
-
- $this->assertTrue( $this->wp_fonts->query( $query_handle, 'enqueued' ) );
- }
-
- /**
- * @dataProvider data_valid_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_enqueued_query_should_fail_when_not_registered_but_enqueued( $query_handle ) {
- $this->wp_fonts->enqueue( $query_handle );
-
- $this->assertFalse( $this->wp_fonts->query( $query_handle, 'enqueued' ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_invalid_query() {
- return array(
- 'DM Sans' => array( 'DM Sans' ),
- 'roboto' => array( 'roboto' ),
- 'my-font' => array( 'my-font' ),
- );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_valid_query() {
- return array(
- 'lato' => array( 'lato' ),
- 'merriweather' => array( 'merriweather' ),
- 'Source Serif Pro' => array( 'source-serif-pro' ),
- );
- }
-
- public function test_done_query_should_fail_when_no_variations() {
- $this->wp_fonts->register_provider( 'local', WP_Fonts_Provider_Local::class );
- $this->setup_registry();
- $this->wp_fonts->enqueue( 'lato' );
-
- $this->wp_fonts->do_items( 'lato' );
-
- $this->assertFalse( $this->wp_fonts->query( 'lato', 'done' ) );
- }
-
- /**
- * @dataProvider data_done_query
- *
- * @param string $query_handle Handle to test.
- */
- public function test_done_query_should_succeed_when_registered_and_enqueued( $query_handle ) {
- $this->wp_fonts->register_provider( 'local', WP_Fonts_Provider_Local::class );
- $this->setup_registry();
- $this->wp_fonts->enqueue( $query_handle );
-
- // Process the fonts while ignoring all the printed output.
- $this->expectOutputRegex( '`.`' );
- $this->wp_fonts->do_items( $query_handle );
- $this->getActualOutput();
-
- $this->assertTrue( $this->wp_fonts->query( $query_handle, 'done' ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_done_query() {
- return array(
- 'merriweather' => array( 'merriweather' ),
- 'Source Serif Pro' => array( 'source-serif-pro' ),
- );
- }
-
- private function setup_registry() {
- foreach ( $this->get_registered_local_fonts() as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $this->wp_fonts );
- }
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/registerProvider.php b/phpunit/tests/fonts-api/wpFonts/registerProvider.php
deleted file mode 100644
index c67976e0a68655..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/registerProvider.php
+++ /dev/null
@@ -1,116 +0,0 @@
-assertTrue( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return true' );
- $this->assertSame( $expected, $wp_fonts->get_providers(), 'Provider "' . $provider_id . '" should be registered in providers queue' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_register_providers() {
- return array(
- 'mock' => array(
- 'provider_id' => 'mock',
- 'class' => Mock_Provider::class,
- 'expected' => array(
- 'mock' => array(
- 'class' => Mock_Provider::class,
- 'fonts' => array(),
- ),
- ),
- ),
- 'local' => array(
- 'provider_id' => 'local',
- 'class' => WP_Fonts_Provider_Local::class,
- 'expected' => array(
- 'local' => array(
- 'class' => WP_Fonts_Provider_Local::class,
- 'fonts' => array(),
- ),
- ),
- ),
- );
- }
-
- public function test_should_register_multiple_providers() {
- $wp_fonts = new WP_Fonts();
- $providers = $this->get_provider_definitions();
- foreach ( $providers as $provider ) {
- $this->assertTrue( $wp_fonts->register_provider( $provider['id'], $provider['class'] ), 'WP_Fonts::register_provider() should return true for provider ' . $provider['id'] );
- }
-
- $expected = array(
- 'mock' => array(
- 'class' => $providers['mock']['class'],
- 'fonts' => array(),
- ),
- 'local' => array(
- 'class' => $providers['local']['class'],
- 'fonts' => array(),
- ),
- );
-
- $this->assertSame( $expected, $wp_fonts->get_providers(), 'Both local and mock providers should be registered' );
- }
-
- /**
- * @dataProvider data_invalid_providers
- *
- * @param string $provider_id Provider ID.
- * @param string $class_name Provider class name.
- */
- public function test_should_not_register( $provider_id, $class_name ) {
- $wp_fonts = new WP_Fonts();
-
- $this->assertFalse( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return false' );
- $this->assertArrayNotHasKey( $provider_id, $wp_fonts->get_providers(), 'Both local and mock providers should be registered' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_invalid_providers() {
- return array(
- 'provider_id is empty' => array(
- 'provider_id' => '',
- 'class' => Mock_Provider::class,
- ),
- 'class is empty' => array(
- 'provider_id' => 'local',
- 'class' => '',
- ),
- 'class does not exist' => array(
- 'provider_id' => 'doesnotexist',
- 'class' => 'Provider_Does_Not_Exist',
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/remove.php b/phpunit/tests/fonts-api/wpFonts/remove.php
deleted file mode 100644
index 208bd58c3d2c30..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/remove.php
+++ /dev/null
@@ -1,118 +0,0 @@
-remove( array( 'handle-1', 'handle2' ) );
-
- $this->assertEmpty( $wp_fonts->registered );
- }
-
- /**
- * @dataProvider data_remove_when_registered
- *
- * @param array $handles Handles to remove.
- * @param array $expected Expected handles are running test.
- */
- public function test_should_remove_when_registered( array $handles, array $expected ) {
- $wp_fonts = new WP_Fonts();
- $wp_fonts->registered = $this->generate_registered_queue();
-
- $wp_fonts->remove( $handles );
-
- $this->assertSameSets( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing handles' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_remove_when_registered() {
- $all = array(
- 'handle-1',
- 'handle-2',
- 'handle-3',
- 'handle-4',
- 'handle-5',
- 'handle-6',
- 'handle-7',
- 'handle-8',
- 'handle-9',
- 'handle-10',
- );
-
- return array(
- 'remove none' => array(
- 'handles' => array(),
- 'expected' => $all,
- ),
- 'remove handle-5' => array(
- 'handles' => array( 'handle-5' ),
- 'expected' => array(
- 'handle-1',
- 'handle-2',
- 'handle-3',
- 'handle-4',
- 'handle-6',
- 'handle-7',
- 'handle-8',
- 'handle-9',
- 'handle-10',
- ),
- ),
- 'remove 2 from start and end' => array(
- 'handles' => array( 'handle-1', 'handle-2', 'handle-9', 'handle-10' ),
- 'expected' => array(
- 'handle-3',
- 'handle-4',
- 'handle-5',
- 'handle-6',
- 'handle-7',
- 'handle-8',
- ),
- ),
- 'remove all' => array(
- 'handles' => $all,
- 'expected' => array(),
- ),
- 'remove only registered' => array(
- 'handles' => array( 'handle-1', 'handle-10', 'handle-abc', 'handle-5' ),
- 'expected' => array(
- 'handle-2',
- 'handle-3',
- 'handle-4',
- 'handle-6',
- 'handle-7',
- 'handle-8',
- 'handle-9',
- ),
- ),
- );
- }
-
- private function generate_registered_queue() {
- $queue = array();
- for ( $num = 1; $num <= 10; $num++ ) {
- $handle = "handle-{$num}";
- $queue[ $handle ] = $num;
- }
-
- return $queue;
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php b/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php
deleted file mode 100644
index 6e762307d05207..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/removeFontFamily.php
+++ /dev/null
@@ -1,89 +0,0 @@
-setup_registration_mocks( $inputs, $wp_fonts );
- // Test the before state, just to make sure.
- $this->assertArrayHasKey( $font_family, $wp_fonts->registered, 'Registered queue should contain the font family before remove' );
- $this->assertSame( $registered_handles, array_keys( $wp_fonts->registered ), 'Font family and variations should be registered before remove' );
-
- $wp_fonts->remove_font_family( $font_family );
-
- $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' );
- $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' );
- }
-
- /**
- * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations
- *
- * @param string $font_family Font family to test.
- * @param array $inputs Font family(ies) and variations to pre-register.
- * @param array $registered_handles Not used.
- * @param array $expected Array of expected handles.
- */
- public function test_should_bail_out_when_not_registered( $font_family, array $inputs, array $registered_handles, array $expected ) {
- $wp_fonts = new WP_Fonts();
- unset( $inputs[ $font_family ] );
- $this->setup_registration_mocks( $inputs, $wp_fonts );
-
- $wp_fonts->remove_font_family( $font_family );
-
- $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' );
- $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' );
- }
-
- /**
- * Integration test for removing a font family and all of its variation when font family is registered.
- *
- * @dataProvider data_one_to_many_font_families_and_zero_to_many_variations
- *
- * @param string $font_family Font family to test.
- * @param array $inputs Font family(ies) and variations to pre-register.
- * @param array $registered_handles Expected handles after registering.
- * @param array $expected Array of expected handles.
- */
- public function test_should_deregister_when_registered( $font_family, array $inputs, array $registered_handles, array $expected ) {
- $wp_fonts = new WP_Fonts();
- // Register all font families and their variations.
- foreach ( $inputs as $input_font_family => $variations ) {
- $handle = $wp_fonts->add_font_family( $input_font_family );
- foreach ( $variations as $variation_handle => $variation ) {
- if ( ! is_string( $variation_handle ) ) {
- $variation_handle = '';
- }
- $wp_fonts->add_variation( $handle, $variation, $variation_handle );
- }
- }
- // Test the before state, just to make sure.
- $this->assertArrayHasKey( $font_family, $wp_fonts->registered, 'Registered queue should contain the font family before remove' );
- $this->assertSame( $registered_handles, array_keys( $wp_fonts->registered ), 'Font family and variations should be registered before remove' );
-
- $wp_fonts->remove_font_family( $font_family );
-
- $this->assertArrayNotHasKey( $font_family, $wp_fonts->registered, 'Registered queue should not contain the font family' );
- $this->assertSame( $expected, array_keys( $wp_fonts->registered ), 'Registered queue should match after removing font family' );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFonts/removeVariation.php b/phpunit/tests/fonts-api/wpFonts/removeVariation.php
deleted file mode 100644
index 508c8ce264d8fb..00000000000000
--- a/phpunit/tests/fonts-api/wpFonts/removeVariation.php
+++ /dev/null
@@ -1,278 +0,0 @@
-wp_fonts = new WP_Fonts();
- $this->fonts_to_register = $this->get_registered_local_fonts();
- }
-
- /**
- * Sets up the unit test by mocking the WP_Dependencies object using stdClass and
- * registering each font family directly to the WP_Fonts::$registered property
- * and its variations to the mocked $deps property.
- */
- private function setup_unit_test() {
- $this->setup_registration_mocks( $this->fonts_to_register, $this->wp_fonts );
- }
-
- /**
- * Sets up the integration test by properly registering each font family and its variations
- * by using the WP_Fonts::add() and WP_Fonts::add_variation() methods.
- */
- private function setup_integration_test() {
- foreach ( $this->fonts_to_register as $font_family_handle => $variations ) {
- $this->setup_register( $font_family_handle, $variations, $this->wp_fonts );
- }
- }
-
- /**
- * Testing the test setup to ensure it works.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_mocked_setup( $font_family_handle, $variation_handle ) {
- $this->setup_unit_test();
-
- $this->assertArrayHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be in the registered queue before removal' );
- $this->assertContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should be in its font family deps before removal' );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered
- *
- * @param string $font_family Font family name.
- * @param string $font_family_handle Font family handle.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_unit_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) {
- // Set up the test.
- unset( $this->fonts_to_register[ $font_family ] );
- $this->setup_unit_test();
- $registered_queue = $this->wp_fonts->registered;
-
- // Run the tests.
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertSame( $registered_queue, $this->wp_fonts->registered, 'Registered queue should not have changed' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_should_do_nothing_when_variation_and_font_family_not_registered
- *
- * @param string $font_family Font family name.
- * @param string $font_family_handle Font family handle.
- * @param string $variation_handle Variation handle to remove.
- */
- public function test_should_do_nothing_when_variation_and_font_family_not_registered( $font_family, $font_family_handle, $variation_handle ) {
- // Set up the test.
- unset( $this->fonts_to_register[ $font_family ] );
- $this->setup_integration_test();
- $registered_queue = $this->wp_fonts->get_registered();
-
- // Run the tests.
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $font_family_handle, $this->wp_fonts->registered, 'Font family should not be registered' );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertSameSets( $registered_queue, $this->wp_fonts->get_registered(), 'Registered queue should not have changed' );
- }
-
- /**
- * Data provider for testing removal of variations.
- *
- * @return array
- */
- public function data_should_do_nothing_when_variation_and_font_family_not_registered() {
- return array(
- 'Font with 1 variation' => array(
- 'font_family' => 'merriweather',
- 'font_family_handle' => 'merriweather',
- 'variation_handle' => 'merriweather-200-900-normal',
- ),
- 'Font with multiple variations' => array(
- 'font_family' => 'Source Serif Pro',
- 'font_family_handle' => 'source-serif-pro',
- 'variation_handle' => 'Source Serif Pro-300-normal',
- ),
- );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
- $this->setup_remove_variation_from_registered( $variation_handle );
-
- // Run the tests.
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_only_remove_from_font_family_deps_when_variation_not_in_queue( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
- $this->setup_remove_variation_from_registered( $variation_handle );
-
- // Run the tests.
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variant should not be registered' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
- $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' );
-
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
-
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_remove_variation_from_registered_queue_though_font_family_not_registered( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
- $this->setup_remove_from_font_family_deps( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should not be in its font family deps before removal' );
-
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
-
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Unit test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_unit_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_unit_test();
-
- $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' );
-
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Integration test.
- *
- * @dataProvider data_remove_variations
- *
- * @param string $font_family_handle Font family for the variation.
- * @param string $variation_handle Variation handle to remove.
- * @param array $expected Expected results.
- */
- public function test_should_remove_variation_from_queue_and_font_family_deps( $font_family_handle, $variation_handle, $expected ) {
- // Set up the test.
- $this->setup_integration_test();
-
- $this->assertArrayHasKey( $variation_handle, array_flip( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Variation should be in its font family deps before removal' );
-
- $this->wp_fonts->remove_variation( $font_family_handle, $variation_handle );
-
- $this->assertArrayNotHasKey( $variation_handle, $this->wp_fonts->registered, 'Variation should be not be in registered queue' );
- $this->assertNotContains( $variation_handle, $this->wp_fonts->registered[ $font_family_handle ]->deps, 'Variation should not be its font family deps' );
- $this->assertSameSets( $expected['font_family_deps'], array_values( $this->wp_fonts->registered[ $font_family_handle ]->deps ), 'Only the tested variation handle should be removed from font family deps' );
- }
-
- /**
- * Remove the variation handle from the font family's deps.
- *
- * @param string $font_family_handle Font family.
- * @param string $variation_handle The variation handle to remove.
- */
- private function setup_remove_from_font_family_deps( $font_family_handle, $variation_handle ) {
- foreach ( $this->wp_fonts->registered[ $font_family_handle ]->deps as $index => $vhandle ) {
- if ( $variation_handle !== $vhandle ) {
- continue;
- }
- unset( $this->wp_fonts->registered[ $font_family_handle ]->deps[ $index ] );
- break;
- }
- }
-
- /**
- * Removes the variation from the WP_Fonts::$registered queue.
- *
- * @param string $variation_handle The variation handle to remove.
- */
- private function setup_remove_variation_from_registered( $variation_handle ) {
- unset( $this->wp_fonts->registered[ $variation_handle ] );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsProviderLocal.php b/phpunit/tests/fonts-api/wpFontsProviderLocal.php
deleted file mode 100644
index f9319390c3b6b5..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsProviderLocal.php
+++ /dev/null
@@ -1,180 +0,0 @@
-provider = new WP_Fonts_Provider_Local();
-
- $this->set_up_theme();
- }
-
- public function tear_down() {
- // Restore the original theme directory setup.
- $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
- wp_clean_themes_cache();
- unset( $GLOBALS['wp_themes'] );
-
- parent::tear_down();
- }
-
- /**
- * @covers WP_Fonts_Provider_Local::set_fonts
- */
- public function test_set_fonts() {
- $fonts = array(
- 'source-serif-pro-200-900-normal-local' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- ),
- 'source-serif-pro-200-900-italic-local' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'italic',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- ),
- );
-
- $this->provider->set_fonts( $fonts );
-
- $property = $this->get_fonts_property();
- $this->assertSame( $fonts, $property->getValue( $this->provider ) );
- }
-
- /**
- * @covers WP_Fonts_Provider_Local::get_css
- *
- * @dataProvider data_get_css_print_styles
- *
- * @param array $fonts Prepared fonts (to store in WP_Fonts_Provider_Local::$fonts property).
- * @param string $expected Expected CSS.
- */
- public function test_get_css( array $fonts, $expected ) {
- $property = $this->get_fonts_property();
- $property->setValue( $this->provider, $fonts );
-
- $this->assertSame( $expected['font-face-css'], $this->provider->get_css() );
- }
-
- /**
- * @covers WP_Fonts_Provider_Local::print_styles
- *
- * @dataProvider data_get_css_print_styles
- *
- * @param array $fonts Prepared fonts (to store in WP_Fonts_Provider_Local::$fonts property).
- * @param string $expected Expected CSS.
- */
- public function test_print_styles( array $fonts, $expected ) {
- $property = $this->get_fonts_property();
- $property->setValue( $this->provider, $fonts );
-
- $expected_output = sprintf( $expected['style-element'], $expected['font-face-css'] );
- $this->expectOutputString( $expected_output );
- $this->provider->print_styles();
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_get_css_print_styles() {
- return array(
- 'truetype format' => array(
- 'fonts' => array(
- 'open-sans-bold-italic-local' => array(
- 'provider' => 'local',
- 'font-family' => 'Open Sans',
- 'font-style' => 'italic',
- 'font-weight' => 'bold',
- 'src' => 'http://example.org/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf',
- ),
- ),
- 'expected' => array(
- 'style-element' => "\n",
- 'font-face-css' => << array(
- 'fonts' => array(
- 'source-serif-pro-200-900-normal-local' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- ),
- 'source-serif-pro-400-900-italic-local' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'italic',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- ),
- ),
- 'expected' => array(
- 'style-element' => "\n",
- 'font-face-css' => <<theme_root = realpath( GUTENBERG_DIR_TESTDATA . '/themedir1' );
- $this->orig_theme_dir = $GLOBALS['wp_theme_directories'];
- $GLOBALS['wp_theme_directories'] = array( $this->theme_root );
-
- $theme_root_callback = function () {
- return $this->theme_root;
- };
- add_filter( 'theme_root', $theme_root_callback );
- add_filter( 'stylesheet_root', $theme_root_callback );
- add_filter( 'template_root', $theme_root_callback );
-
- // Clear caches.
- wp_clean_themes_cache();
- unset( $GLOBALS['wp_themes'] );
- }
-
- private function get_fonts_property() {
- $property = new ReflectionProperty( $this->provider, 'fonts' );
- $property->setAccessible( true );
-
- return $property;
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php b/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php
deleted file mode 100644
index d6e45561e5e335..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsResolver/addMissingFontsToThemeJson.php
+++ /dev/null
@@ -1,252 +0,0 @@
-assertInstanceOf( WP_Theme_JSON_Gutenberg::class, $actual, 'Instance of WP_Theme_JSON_Gutenberg should be returned' );
- }
-
- /**
- * @dataProvider data_themes
- *
- * @param string $theme Theme to use.
- */
- public function test_should_bail_out_when_no_registered_fonts( $theme ) {
- switch_theme( $theme );
-
- $data = new WP_Theme_JSON_Gutenberg( self::$theme_json_data[ $theme ] );
- $actual = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( $data );
-
- $this->assertEmpty( wp_fonts()->get_registered_font_families(), 'No fonts should be registered in Fonts API' );
- $this->assertSame( $data, $actual, 'Same instance of WP_Theme_JSON_Gutenberg should be returned' );
- }
-
- /**
- * Data Provider.
- *
- * @return array
- */
- public function data_themes() {
- return array(
- 'no fonts defined' => array( 'block-theme' ),
- 'no fonts registered' => array( static::FONTS_THEME ),
- );
- }
-
- /**
- * @dataProvider data_should_add_non_theme_json_fonts
- *
- * @param string $theme Theme to use.
- * @param array $fonts Fonts to register.
- * @param array $expected Expected fonts to be added.
- */
- public function test_should_add_non_theme_json_fonts( $theme, $fonts, $expected ) {
- switch_theme( static::FONTS_THEME );
-
- // Register the fonts.
- wp_register_fonts( $fonts );
-
- $data = new WP_Theme_JSON_Gutenberg( self::$theme_json_data[ $theme ] );
- $actual = WP_Fonts_Resolver::add_missing_fonts_to_theme_json( $data );
-
- $this->assertNotSame( $data, $actual, 'New instance of WP_Theme_JSON_Gutenberg should be returned' );
- $actual_raw_data = $actual->get_raw_data();
-
- $this->assertArrayHasKey( 'typography', $actual_raw_data['settings'] );
- $this->assertArrayHasKey( 'fontFamilies', $actual_raw_data['settings']['typography'] );
- $this->assertArrayHasKey( 'theme', $actual_raw_data['settings']['typography']['fontFamilies'] );
-
- $this->assertContains(
- $expected,
- $actual_raw_data['settings']['typography']['fontFamilies']['theme'],
- 'Fonts should be added after running WP_Fonts_Resolver::add_missing_fonts_to_theme_json()'
- );
- }
-
- /**
- * Data Provider.
- *
- * @return array
- */
- public function data_should_add_non_theme_json_fonts() {
- $lato = array(
- 'Lato' => array(
- array(
- 'font-family' => 'Lato',
- 'font-style' => 'normal',
- 'font-weight' => '400',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2',
- ),
- array(
- 'font-family' => 'Lato',
- 'font-style' => 'italic',
- 'font-weight' => '400',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2',
- ),
- ),
- );
-
- $expected_lato = array(
- 'fontFamily' => 'Lato',
- 'name' => 'Lato',
- 'slug' => 'lato',
- 'fontFace' => array(
- 'lato-400-normal' => array(
- 'origin' => 'gutenberg_wp_fonts_api',
- 'provider' => 'local',
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'fontDisplay' => 'fallback',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2',
- ),
- 'lato-400-italic' => array(
- 'origin' => 'gutenberg_wp_fonts_api',
- 'provider' => 'local',
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'fontDisplay' => 'fallback',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2',
- ),
- ),
- );
-
- return array(
- 'theme with no fonts defined' => array(
- 'theme' => 'block-theme',
- 'fonts' => $lato,
- 'expected' => $expected_lato,
- ),
- 'theme with fonts: new fonts not in theme' => array(
- 'theme' => static::FONTS_THEME,
- 'fonts' => $lato,
- 'expected' => $expected_lato,
- ),
-
- /*
- * @TODO Add these tests fixing https://github.com/WordPress/gutenberg/issues/50047.
- *
- 'theme with fonts: new variations registered' => array(
- 'theme' => static::FONTS_THEME,
- 'fonts' => array(
- 'DM Sans' => array(
- 'dm-sans-500-normal' => array(
- 'font-family' => 'DM Sans',
- 'font-style' => 'normal',
- 'font-weight' => '500',
- 'src' => 'https://example.com/tests/assets/fonts/dm-sans/DMSans-Medium.woff2',
- ),
- 'dm-sans-500-italic' => array(
- 'font-family' => 'DM Sans',
- 'font-style' => 'italic',
- 'font-weight' => '500',
- 'src' => 'https://example.com/tests/assets/fonts/dm-sans/DMSans-Medium.woff2',
- ),
- ),
- ),
- 'expected' => array(
- 'fontFace' => array(
- array(
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Regular.woff2' ),
- ),
- array(
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Regular-Italic.woff2' ),
- ),
- 'dm-sans-500-normal' => array(
- 'origin' => 'gutenberg_wp_fonts_api',
- 'provider' => 'local',
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'fontDisplay' => 'fallback',
- 'src' => array( get_stylesheet_directory_uri() . 'assets/fonts/dm-sans/DMSans-Medium.woff2' ),
- ),
- 'dm-sans-500-italic' => array(
- 'origin' => 'gutenberg_wp_fonts_api',
- 'provider' => 'local',
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'italic',
- 'fontWeight' => '500',
- 'fontDisplay' => 'fallback',
- 'src' => array( get_stylesheet_directory_uri() . 'assets/fonts/dm-sans/DMSans-Medium-Italic.woff2' ),
- ),
- array(
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'normal',
- 'fontWeight' => '700',
- 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Bold.woff2' ),
- ),
- array(
- 'fontFamily' => 'DM Sans',
- 'fontStretch' => 'normal',
- 'fontStyle' => 'italic',
- 'fontWeight' => '700',
- 'src' => array( 'file:./assets/fonts/dm-sans/DMSans-Bold-Italic.woff2' ),
- ),
- ),
- 'fontFamily' => '"DM Sans", sans-serif',
- 'name' => 'DM Sans',
- 'slug' => 'dm-sans',
- ),
- ),
- */
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php b/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php
deleted file mode 100644
index 9ff01f1c3166cd..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsResolver/enqueueUserSelectedFonts.php
+++ /dev/null
@@ -1,131 +0,0 @@
-user->create(
- array(
- 'role' => 'administrator',
- 'user_email' => 'administrator@example.com',
- )
- );
- }
-
- /**
- * @dataProvider data_should_not_enqueue_when_no_user_selected_fonts
- *
- * @param array $styles Optional. Test styles. Default empty array.
- */
- public function test_should_not_enqueue_when_no_user_selected_fonts( $styles = array() ) {
- $this->set_up_global_styles( $styles );
-
- $mock = $this->set_up_mock( 'enqueue' );
- $mock->expects( $this->never() )
- ->method( 'enqueue' );
-
- $expected = array();
- $this->assertSame( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_not_enqueue_when_no_user_selected_fonts() {
- return array(
- 'no user-selected styles' => array(),
- 'invalid element' => array(
- array(
- 'elements' => array(
- 'invalid' => array(
- 'typography' => array(
- 'fontFamily' => 'var:preset|font-family|font1',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- ),
- ),
- ),
- ),
- ),
- );
- }
-
- /**
- * @dataProvider data_should_enqueue_when_user_selected_fonts
- *
- * @param array $styles Test styles.
- * @param array $expected Expected results.
- */
- public function test_should_enqueue_when_user_selected_fonts( $styles, $expected ) {
- $mock = $this->set_up_mock( 'enqueue' );
- $mock->expects( $this->once() )
- ->method( 'enqueue' )
- ->with(
- $this->identicalTo( $expected )
- );
-
- $this->set_up_global_styles( $styles );
-
- $this->assertSameSets( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_enqueue_when_user_selected_fonts() {
- $global_styles = $this->get_mock_user_selected_fonts_global_styles();
-
- return array(
- 'heading, caption, text' => array(
- 'styles' => $global_styles['font1'],
- 'expected' => array( 'font1' ),
- ),
- 'heading, button' => array(
- 'styles' => $global_styles['font2'],
- 'expected' => array( 'font2' ),
- ),
- 'text' => array(
- 'styles' => $global_styles['font3'],
- 'expected' => array( 'font3' ),
- ),
- 'all' => array(
- 'styles' => $global_styles['all'],
- 'expected' => array(
- 0 => 'font1',
- // font1 occurs 2 more times and gets removed as duplicates.
- 3 => 'font2',
- 4 => 'font3',
- ),
- ),
- 'all with invalid element' => array(
- 'styles' => $global_styles['all with invalid element'],
- 'expected' => array(
- 0 => 'font1',
- // font1 occurs 2 more times and gets removed as duplicates.
- 3 => 'font2',
- // Skips font2 for the "invalid" element.
- 4 => 'font3',
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php b/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php
deleted file mode 100644
index 0186dbf34f3fdc..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php
+++ /dev/null
@@ -1,297 +0,0 @@
- array(
- // From theme.json.
- 'dm-sans',
- 'source-serif-pro',
- // From style variation.
- 'open-sans',
- ),
- );
-
- public static function set_up_before_class() {
- self::$requires_switch_theme_fixtures = true;
-
- parent::set_up_before_class();
- }
-
- public function test_should_bails_out_when_no_fonts_defined() {
- switch_theme( 'block-theme' );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
- $wp_fonts = wp_fonts();
-
- $this->assertEmpty( $wp_fonts->get_registered() );
- $this->assertEmpty( $wp_fonts->get_enqueued() );
- }
-
- public function test_should_register_and_enqueue_style_variation_fonts() {
- switch_theme( static::FONTS_THEME );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
- $wp_fonts = wp_fonts();
-
- $this->assertContains( 'open-sans', $wp_fonts->get_registered_font_families(), 'Font families should be registered' );
- $this->assertContains( 'open-sans', $wp_fonts->get_enqueued(), 'Font families should be enqueued' );
- }
-
- /**
- * Tests all font families are registered and enqueued. "All" means all font families from
- * the theme's theme.json and within the style variations.
- */
- public function test_should_register_and_enqueue_all_defined_font_families() {
- switch_theme( static::FONTS_THEME );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
- $wp_fonts = wp_fonts();
-
- $expected = static::FONT_FAMILIES[ static::FONTS_THEME ];
- $this->assertSameSetsWithIndex( $expected, $wp_fonts->get_registered_font_families(), 'Font families should be registered' );
- $this->assertSameSetsWithIndex( $expected, $wp_fonts->get_enqueued(), 'Font families should be enqueued' );
- }
-
- /**
- * Test ensures duplicate fonts and variations in the style variations
- * are not re-registered.
- *
- * The Dm Sans fonts are duplicated in the theme's /styles/variations-duplicate-fonts.json.
- */
- public function test_should_not_reregister_duplicate_fonts_from_style_variations() {
- switch_theme( static::FONTS_THEME );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
- $wp_fonts = wp_fonts();
-
- // Font families are not duplicated.
- $this->assertSameSetsWithIndex(
- static::FONT_FAMILIES[ static::FONTS_THEME ],
- $wp_fonts->get_registered_font_families(),
- 'Font families should not be duplicated'
- );
-
- // Font variations are not duplicated.
- $this->assertSameSets(
- array(
- // From theme.json.
- 'dm-sans',
- 'dm-sans-400-normal',
- 'dm-sans-400-italic',
- 'dm-sans-700-normal',
- 'dm-sans-700-italic',
- 'source-serif-pro',
- 'source-serif-pro-200-900-normal',
- 'source-serif-pro-200-900-italic',
- // From style variation.
- 'open-sans',
- 'open-sans-400-normal',
- 'open-sans-400-italic',
- 'dm-sans-500-normal',
- 'dm-sans-500-italic',
- ),
- $wp_fonts->get_registered(),
- 'Font families and their variations should not be duplicated'
- );
- }
-
- /**
- * @dataProvider data_should_replace_src_file_placeholder
- *
- * @param string $handle Variation's handle.
- * @param string $expected Expected src.
- */
- public function test_should_replace_src_file_placeholder( $handle, $expected ) {
- switch_theme( static::FONTS_THEME );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
-
- $variation = wp_fonts()->registered[ $handle ];
- $actual = array_pop( $variation->src );
- $expected = get_stylesheet_directory_uri() . $expected;
-
- $this->assertStringNotContainsString( 'file:./', $actual, 'Font src should not contain the "file:./" placeholder' );
- $this->assertSame( $expected, $actual, 'Font src should be an URL to its file' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_replace_src_file_placeholder() {
- return array(
- // Theme's theme.json.
- 'DM Sans: 400 normal' => array(
- 'handle' => 'dm-sans-400-normal',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Regular.woff2',
- ),
- 'DM Sans: 400 italic' => array(
- 'handle' => 'dm-sans-400-italic',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Regular-Italic.woff2',
- ),
- 'DM Sans: 700 normal' => array(
- 'handle' => 'dm-sans-700-normal',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Bold.woff2',
- ),
- 'DM Sans: 700 italic' => array(
- 'handle' => 'dm-sans-700-italic',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Bold-Italic.woff2',
- ),
- 'Source Serif Pro: 200-900 normal' => array(
- 'handle' => 'source-serif-pro-200-900-normal',
- 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- ),
- 'Source Serif Pro: 200-900 italic' => array(
- 'handle' => 'source-serif-pro-200-900-italic',
- 'expected' => '/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- ),
-
- // Style Variation: variation-with-new-font-family.json.
- 'Style Variation: new font-family' => array(
- 'handle' => 'open-sans-400-normal',
- 'expected' => '/assets/fonts/open-sans/OpenSans-VariableFont_wdth,wght.tff',
- ),
- 'Style Variation: new font-family italic variation' => array(
- 'handle' => 'open-sans-400-italic',
- 'expected' => '/assets/fonts/open-sans/OpenSans-Italic-VariableFont_wdth,wght.tff',
- ),
-
- // Style Variation: variation-with-new-variation.json.
- 'Style Variation: new medium variation' => array(
- 'handle' => 'dm-sans-500-normal',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Medium.woff2',
- ),
- 'Style Variation: new medium italic variation' => array(
- 'handle' => 'dm-sans-500-italic',
- 'expected' => '/assets/fonts/dm-sans/DMSans-Medium-Italic.woff2',
- ),
- );
- }
-
- public function test_should_convert_font_face_properties_into_kebab_case() {
- switch_theme( static::FONTS_THEME );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
-
- // Testing only one variation since this theme's fonts use the same properties.
- $variation = wp_fonts()->registered['dm-sans-400-normal'];
- $actual_properties = $variation->extra['font-properties'];
-
- $this->assertArrayHasKey( 'font-family', $actual_properties, 'fontFamily should have been converted into font-family' );
- $this->assertArrayNotHasKey( 'fontFamily', $actual_properties, 'fontFamily should not exist.' );
- $this->assertArrayHasKey( 'font-stretch', $actual_properties, 'fontStretch should have been converted into font-stretch' );
- $this->assertArrayNotHasKey( 'fontStretch', $actual_properties, 'fontStretch should not exist' );
- $this->assertArrayHasKey( 'font-style', $actual_properties, 'fontStyle should have been converted into font-style' );
- $this->assertArrayNotHasKey( 'fontStyle', $actual_properties, 'fontStyle should not exist.' );
- $this->assertArrayHasKey( 'font-weight', $actual_properties, 'fontWeight should have been converted into font-weight' );
- $this->assertArrayNotHasKey( 'fontWeight', $actual_properties, 'fontWeight should not exist' );
- }
-
- /**
- * Tests that WP_Fonts_Resolver::register_fonts_from_theme_json() skips fonts that are already registered
- * in the Fonts API. How does it do that? Using the 'origin' property when checking each variation.
- * This property is added when WP_Theme_JSON_Resolver_Gutenberg::get_merged_data() runs.
- *
- * To simulate this scenario, a font is registered first, but not enqueued. Then after running,
- * it checks if the WP_Fonts_Resolver::register_fonts_from_theme_json() enqueued the font. If no, then
- * it was skipped as expected.
- */
- public function test_should_skip_registered_fonts() {
- switch_theme( static::FONTS_THEME );
-
- // Register Lato font.
- wp_register_fonts(
- array(
- 'Lato' => array(
- array(
- 'font-family' => 'Lato',
- 'font-style' => 'normal',
- 'font-weight' => '400',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular.woff2',
- ),
- array(
- 'font-family' => 'Lato',
- 'font-style' => 'italic',
- 'font-weight' => '400',
- 'src' => 'https://example.com/tests/assets/fonts/lato/Lato-Regular-Italic.woff2',
- ),
- ),
- )
- );
-
- // Pre-check to ensure no fonts are enqueued.
- $this->assertEmpty( wp_fonts()->get_enqueued(), 'No fonts should be enqueued before running WP_Fonts_Resolver::register_fonts_from_theme_json()' );
-
- /*
- * When this function runs, it invokes WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(),
- * which will include the Lato fonts with a 'origin' property set in each variation.
- */
- WP_Fonts_Resolver::register_fonts_from_theme_json();
-
- $actual_enqueued_fonts = wp_fonts()->get_enqueued();
-
- $this->assertNotContains( 'lato', $actual_enqueued_fonts, 'Lato font-family should not be enqueued' );
- $this->assertSameSets( static::FONT_FAMILIES[ static::FONTS_THEME ], $actual_enqueued_fonts, 'Only the theme font families should be enqueued' );
- }
-
- public function test_should_skip_when_font_face_not_defined() {
- switch_theme( static::FONTS_THEME );
- $expected_font_family = 'source-serif-pro';
-
- /**
- * Callback that removes the 'fontFace' of the expected font family from the theme's theme.json data.
- * This callback is invoked at the start of WP_Fonts_Resolver::register_fonts_from_theme_json() before processing
- * within that function. How? It's in the call stack of WP_Theme_JSON_Resolver_Gutenberg::get_merged_data().
- *
- * @param WP_Theme_JSON_Data_Gutenberg| WP_Theme_JSON_Data $theme_json_data Instance of the Data object.
- * @return WP_Theme_JSON_Data_Gutenberg| WP_Theme_JSON_Data Modified instance.
- * @throws ReflectionException
- */
- $remove_expected_font_family = static function ( $theme_json_data ) use ( $expected_font_family ) {
- // Need to get the underlying data array which is in WP_Theme_JSON_Gutenberg | WP_Theme_JSON object.
- $property = new ReflectionProperty( $theme_json_data, 'theme_json' );
- $property->setAccessible( true );
- $theme_json_object = $property->getValue( $theme_json_data );
-
- $property = new ReflectionProperty( $theme_json_object, 'theme_json' );
- $property->setAccessible( true );
- $data = $property->getValue( $theme_json_object );
-
- // Loop through the fonts to find the expected font-family to modify.
- foreach ( $data['settings']['typography']['fontFamilies']['theme'] as $index => $definitions ) {
- if ( $expected_font_family !== $definitions['slug'] ) {
- continue;
- }
-
- // Remove the 'fontFace' element, which removes the font's variations.
- unset( $data['settings']['typography']['fontFamilies']['theme'][ $index ]['fontFace'] );
- break;
- }
-
- $theme_json_data->update_with( $data );
-
- return $theme_json_data;
- };
- add_filter( 'wp_theme_json_data_theme', $remove_expected_font_family );
-
- WP_Fonts_Resolver::register_fonts_from_theme_json();
-
- remove_filter( 'wp_theme_json_data_theme', $remove_expected_font_family );
-
- $this->assertNotContains( $expected_font_family, wp_fonts()->get_registered_font_families() );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php b/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php
deleted file mode 100644
index 19b2f15c826ed1..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsUtils/convertFontFamilyIntoHandle.php
+++ /dev/null
@@ -1,84 +0,0 @@
-assertSame( $expected, WP_Fonts_Utils::convert_font_family_into_handle( $font_family ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_valid_input() {
- return array(
- 'font family single word name' => array(
- 'font_family' => 'Merriweather',
- 'expected' => 'merriweather',
- ),
- 'font family multiword name' => array(
- 'font_family' => 'Source Sans Pro',
- 'expected' => 'source-sans-pro',
- ),
- 'font family handle delimited by hyphens' => array(
- 'font_family' => 'source-serif-pro',
- 'expected' => 'source-serif-pro',
- ),
- 'font family handle delimited by underscore' => array(
- 'font_family' => 'source_serif_pro',
- 'expected' => 'source_serif_pro',
- ),
- 'font family handle delimited by hyphens and underscore' => array(
- 'font_family' => 'my-custom_font_family',
- 'expected' => 'my-custom_font_family',
- ),
- 'font family handle delimited mixture' => array(
- 'font_family' => 'My custom_font-family',
- 'expected' => 'my-custom_font-family',
- ),
- );
- }
-
- /**
- * @dataProvider data_with_invalid_input
- *
- * @covers WP_Fonts_Utils::convert_font_family_into_handle
- *
- * @param mixed $invalid_input Invalid input.
- */
- public function test_should_not_convert_with_invalid_input( $invalid_input ) {
- $this->assertNull( WP_Fonts_Utils::convert_font_family_into_handle( $invalid_input ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_invalid_input() {
- return array(
- 'empty string' => array( '' ),
- 'integer' => array( 10 ),
- 'font family wrapped in an array' => array( array( 'source-serif-pro' ) ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php b/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php
deleted file mode 100644
index 9268aac8ce372b..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsUtils/convertVariationIntoHandle.php
+++ /dev/null
@@ -1,122 +0,0 @@
-assertSame( $expected, WP_Fonts_Utils::convert_variation_into_handle( $font_family, $variation ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_valid_input() {
- return array(
- 'with only font-weight' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'font-weight' => '400',
- ),
- 'expected' => 'merriweather-400',
- ),
- 'with no font-style' => array(
- 'font_family' => 'source-sans-pro',
- 'variation' => array(
- 'font-weight' => '200 900',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'provider' => 'local',
- ),
- 'expected' => 'source-sans-pro-200-900',
- ),
- 'with font family name and full variant' => array(
- 'font_family' => 'source-sans-pro',
- 'variation' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'expected' => 'source-sans-pro-200-900-normal',
- ),
- );
- }
-
- /**
- * @dataProvider data_with_invalid_input
- *
- * @param string $font_family Font family to test.
- * @param array $invalid_input Variation to test.
- */
- public function tests_should_convert_with_invalid_input( $font_family, $invalid_input ) {
- $this->expectNotice();
- $this->expectNoticeMessage( 'Variant handle could not be determined as font-weight and/or font-style are require' );
-
- $this->assertNull( WP_Fonts_Utils::convert_variation_into_handle( $font_family, $invalid_input ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_invalid_input() {
- return array(
- 'with no font-weight or font-style' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'provider' => 'local',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- ),
- 'with non-string font-weight' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'font-weight' => 400,
- ),
- ),
- 'with non-string font-style' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'font-style' => 0,
- ),
- ),
- 'with empty string font-weight' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'font-weight' => '',
- ),
- ),
- 'with empty string font-style' => array(
- 'font_family' => 'merriweather',
- 'variation' => array(
- 'font-style' => '',
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php b/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php
deleted file mode 100644
index 2b3e3689c4317f..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsUtils/getFontFamilyFromVariation.php
+++ /dev/null
@@ -1,134 +0,0 @@
-assertSame( $expected, WP_Fonts_Utils::get_font_family_from_variation( $variation ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_valid_variation() {
- return array(
- 'keyed by font-family' => array(
- 'variation' => array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'expected' => 'Source Serif Pro',
- ),
- 'keyed by fontFamily and as a handle' => array(
- 'variation' => array(
- 'fontFamily' => 'source-sans-pro',
- 'font-weight' => '200 900',
- 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2',
- 'provider' => 'local',
- ),
- 'expected' => 'source-sans-pro',
- ),
- 'with font family name and full variant' => array(
- 'variation' => array(
- 'provider' => 'local',
- 'font-family' => 'Merriweather',
- 'font-style' => 'normal',
- 'font-weight' => '400 600',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/merriweather.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'expected' => 'Merriweather',
- ),
- );
- }
-
- /**
- * @dataProvider data_with_invalid_input
- *
- * @param array $invalid_variation Variation to test.
- * @param string $expected_message Expected notice message.
- */
- public function test_with_invalid_input( array $invalid_variation, $expected_message ) {
- $this->expectNotice();
- $this->expectNoticeMessage( $expected_message );
-
- $this->assertNull( WP_Fonts_Utils::get_font_family_from_variation( $invalid_variation ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_with_invalid_input() {
- return array(
- 'keyed with underscore' => array(
- 'variation' => array(
- 'provider' => 'local',
- 'font_family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'expected_message' => 'Font family not found.',
- ),
- 'keyed with space' => array(
- 'variation' => array(
- 'font family' => 'Source Sans Pro',
- 'font-weight' => '200 900',
- 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2',
- 'provider' => 'local',
- ),
- 'expected_message' => 'Font family not found.',
- ),
- 'fontFamily => empty string' => array(
- 'variation' => array(
- 'fontFamily' => '',
- 'font-weight' => '200 900',
- 'src' => 'https://example.com/assets/fonts/source-sans-pro/source-sans-pro.ttf.woff2',
- 'provider' => 'local',
- ),
- 'expected_message' => 'Font family not defined in the variation.',
- ),
- 'font-family => empty string' => array(
- 'variation' => array(
- 'provider' => 'local',
- 'font-family' => '',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- 'expected_message' => 'Font family not defined in the variation.',
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php b/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php
deleted file mode 100644
index 3ae48ad52671a2..00000000000000
--- a/phpunit/tests/fonts-api/wpFontsUtils/isDefined.php
+++ /dev/null
@@ -1,61 +0,0 @@
-assertTrue( WP_Fonts_Utils::is_defined( $input ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_when_defined() {
- return array(
- 'name: non empty string' => array( 'Some Font Family' ),
- 'handle: non empty string' => array( 'some-font-family' ),
- );
- }
-
- /**
- * @dataProvider data_when_not_defined
- *
- * @param mixed $invalid_input Input to test.
- */
- public function test_should_return_false_when_not_defined( $invalid_input ) {
- $this->assertFalse( WP_Fonts_Utils::is_defined( $invalid_input ) );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_when_not_defined() {
- return array(
- 'empty string' => array( '' ),
- 'string 0' => array( '0' ),
- 'integer' => array( 10 ),
- 'name wrapped in an array' => array( array( 'Some Font Family' ) ),
- 'handle wrapped in an array' => array( array( 'some-font-family' ) ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpPrintFonts.php b/phpunit/tests/fonts-api/wpPrintFonts.php
deleted file mode 100644
index 40b416aa56421f..00000000000000
--- a/phpunit/tests/fonts-api/wpPrintFonts.php
+++ /dev/null
@@ -1,230 +0,0 @@
-assertSame( array(), wp_print_fonts() );
- }
-
- /**
- * Unit test which mocks WP_Fonts methods.
- *
- * @dataProvider data_mocked_handles
- *
- * @param string|string[] $handles Handles to test.
- */
- public function test_should_return_mocked_handles( $handles ) {
- $mock = $this->set_up_mock( array( 'get_registered_font_families', 'do_items' ) );
- $mock->expects( $this->once() )
- ->method( 'get_registered_font_families' )
- ->will( $this->returnValue( $handles ) );
-
- $mock->expects( $this->once() )
- ->method( 'do_items' )
- ->with(
- $this->identicalTo( $handles )
- )
- ->will( $this->returnValue( $handles ) );
-
- wp_print_fonts( $handles );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_mocked_handles() {
- return array(
- 'font family' => array(
- array( 'my-custom-font' ),
- ),
- 'multiple font families' => array(
- array(
- 'font1',
- 'font2',
- ),
- ),
- );
- }
-
- /**
- * Integration test that registers providers and fonts and then enqueues before
- * testing the printing functionality.
- *
- * @dataProvider data_print_enqueued
- *
- * @param array $setup Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_enqueued( $setup, $expected_done, $expected_output ) {
- $wp_fonts = wp_fonts();
-
- $this->setup_integrated_deps( $setup, $wp_fonts );
-
- $this->expectOutputString( $expected_output );
- $actual_done = wp_print_fonts();
- $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
- }
-
- /**
- * Integration test to validate printing given handles. Rather than mocking internal functionality,
- * it registers providers and fonts but does not enqueue.
- *
- * @dataProvider data_print_enqueued
- *
- * @param array $setup Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_handles_when_not_enqueued( $setup, $expected_done, $expected_output ) {
- $wp_fonts = wp_fonts();
-
- $this->setup_integrated_deps( $setup, $wp_fonts, false );
- // Do not enqueue. Instead, pass the handles to wp_print_fonts().
- $handles = $setup['enqueued'];
- $this->assertEmpty( $wp_fonts->queue, 'No fonts should be enqueued' );
-
- $this->expectOutputString( $expected_output );
- $actual_done = wp_print_fonts( $handles );
- $this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
- }
-
- /**
- * @dataProvider data_should_print_all_registered_fonts_for_iframed_editor
- *
- * @param string $fonts Fonts to register.
- * @param array $expected Expected results.
- */
- public function test_should_print_all_registered_fonts_for_iframed_editor( $fonts, $expected ) {
- wp_register_fonts( $fonts );
-
- $this->expectOutputString( $expected['output'] );
- $actual_done = wp_print_fonts( true );
- $this->assertSameSets( $expected['done'], $actual_done, 'All registered font-family handles should be returned' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_print_all_registered_fonts_for_iframed_editor() {
- $local_fonts = $this->get_registered_local_fonts();
- $font_faces = $this->get_registered_fonts_css();
-
- return array(
- 'Merriweather with 1 variation' => array(
- 'fonts' => array( 'merriweather' => $local_fonts['merriweather'] ),
- 'expected' => array(
- 'done' => array( 'merriweather', 'merriweather-200-900-normal' ),
- 'output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal']
- ),
- ),
- ),
- 'Source Serif Pro with 2 variations' => array(
- 'fonts' => array( 'Source Serif Pro' => $local_fonts['Source Serif Pro'] ),
- 'expected' => array(
- 'done' => array( 'source-serif-pro', 'Source Serif Pro-300-normal', 'Source Serif Pro-900-italic' ),
- 'output' => sprintf(
- "\n",
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
- ),
- 'all fonts' => array(
- 'fonts' => $local_fonts,
- 'expected' => array(
- 'done' => array(
- 'merriweather',
- 'merriweather-200-900-normal',
- 'source-serif-pro',
- 'Source Serif Pro-300-normal',
- 'Source Serif Pro-900-italic',
- ),
- 'output' => sprintf(
- "\n",
- $font_faces['merriweather-200-900-normal'],
- $font_faces['Source Serif Pro-300-normal'],
- $font_faces['Source Serif Pro-900-italic']
- ),
- ),
- ),
- );
- }
-
- /**
- * Integration test for printing user-selected global fonts.
- * This test registers providers and fonts and then enqueues before testing the printing functionality.
- *
- * @dataProvider data_print_user_selected_fonts
- *
- * @param array $global_styles Test set up information for provider, fonts, and enqueued.
- * @param array $expected_done Expected array of printed handles.
- * @param string $expected_output Expected printed output.
- */
- public function test_should_print_user_selected_fonts( $global_styles, $expected_done, $expected_output ) {
- $wp_fonts = wp_fonts();
-
- $setup = array(
- 'provider' => array( 'mock' => $this->get_provider_definitions( 'mock' ) ),
- 'registered' => $this->get_registered_mock_fonts(),
- 'global_styles' => $global_styles,
- );
- $this->setup_integrated_deps( $setup, $wp_fonts, false );
-
- $this->expectOutputString( $expected_output );
- $actual_printed_fonts = wp_print_fonts();
- $this->assertSameSets( $expected_done, $actual_printed_fonts, 'Should print font-faces for given user-selected fonts' );
- }
-
-
- /**
- * Sets up the dependencies for integration test.
- *
- * @param array $setup Dependencies to set up.
- * @param WP_Fonts $wp_fonts Instance of WP_Fonts.
- * @param bool $enqueue Whether to enqueue. Default true.
- */
- private function setup_integrated_deps( array $setup, $wp_fonts, $enqueue = true ) {
- foreach ( $setup['provider'] as $provider ) {
- $wp_fonts->register_provider( $provider['id'], $provider['class'] );
- }
- foreach ( $setup['registered'] as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $wp_fonts );
- }
-
- if ( $enqueue ) {
- $wp_fonts->enqueue( $setup['enqueued'] );
- }
-
- if ( ! empty( $setup['global_styles'] ) ) {
- $this->set_up_global_styles( $setup['global_styles'] );
- }
- }
-}
diff --git a/phpunit/tests/fonts-api/wpRegisterFontProvider.php b/phpunit/tests/fonts-api/wpRegisterFontProvider.php
deleted file mode 100644
index 907355bdc91c82..00000000000000
--- a/phpunit/tests/fonts-api/wpRegisterFontProvider.php
+++ /dev/null
@@ -1,95 +0,0 @@
-set_up_mock( 'register_provider' );
- $mock->expects( $this->once() )
- ->method( 'register_provider' )
- ->with(
- $this->identicalTo( $provider_id ),
- $this->identicalTo( $class_name )
- )
- ->will( $this->returnValue( true ) );
-
- $this->assertTrue( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return true' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_register_providers() {
- return array(
- 'mock' => array(
- 'provider_id' => 'mock',
- 'class' => Mock_Provider::class,
- ),
- 'local' => array(
- 'provider_id' => 'local',
- 'class' => WP_Fonts_Provider_Local::class,
- ),
- );
- }
-
- /**
- * @dataProvider data_invalid_providers
- *
- * @param string $provider_id Provider ID.
- * @param string $class_name Provider class name.
- */
- public function test_should_not_register( $provider_id, $class_name ) {
- $mock = $this->set_up_mock( 'register_provider' );
- $mock->expects( $this->once() )
- ->method( 'register_provider' )
- ->with(
- $this->identicalTo( $provider_id ),
- $this->identicalTo( $class_name )
- )
- ->will( $this->returnValue( false ) );
-
- $this->assertFalse( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return false' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_invalid_providers() {
- return array(
- 'provider_id is empty' => array(
- 'provider_id' => '',
- 'class' => Mock_Provider::class,
- ),
- 'class is empty' => array(
- 'provider_id' => 'local',
- 'class' => '',
- ),
- 'class does not exist' => array(
- 'provider_id' => 'doesnotexist',
- 'class' => 'Provider_Does_Not_Exist',
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts-api/wpRegisterFonts.php b/phpunit/tests/fonts-api/wpRegisterFonts.php
deleted file mode 100644
index c57c1406a6cacc..00000000000000
--- a/phpunit/tests/fonts-api/wpRegisterFonts.php
+++ /dev/null
@@ -1,104 +0,0 @@
-assertSame( $expected['wp_register_fonts'], $actual, 'Font family handle(s) should be returned' );
- $this->assertSame( $expected['get_registered'], $this->get_registered_handles(), 'Web fonts should match registered queue' );
- }
-
- /**
- * @dataProvider data_fonts
- *
- * @param array $fonts Array of fonts to test.
- */
- public function test_should_not_enqueue_on_registration( array $fonts ) {
- wp_register_fonts( $fonts );
- $this->assertEmpty( $this->get_enqueued_handles() );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_fonts() {
- return array(
- 'font family keyed with slug' => array(
- 'fonts' => array(
- 'source-serif-pro' => array(
- array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- ),
- ),
- 'expected' => array(
- 'wp_register_fonts' => array( 'source-serif-pro' ),
- 'get_registered' => array(
- 'source-serif-pro',
- 'source-serif-pro-200-900-normal',
- ),
- ),
- ),
- 'font family keyed with name' => array(
- 'fonts' => array(
- 'Source Serif Pro' => array(
- array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'normal',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- array(
- 'provider' => 'local',
- 'font-family' => 'Source Serif Pro',
- 'font-style' => 'italic',
- 'font-weight' => '200 900',
- 'font-stretch' => 'normal',
- 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
- 'font-display' => 'fallback',
- ),
- ),
- ),
- 'expected' => array(
- 'wp_register_fonts' => array( 'source-serif-pro' ),
- 'get_registered' => array(
- 'source-serif-pro',
- 'source-serif-pro-200-900-normal',
- 'source-serif-pro-200-900-italic',
- ),
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/fontFamilyBackwardsCompatibility.php b/phpunit/tests/fonts/font-library/fontFamilyBackwardsCompatibility.php
new file mode 100644
index 00000000000000..a971bd51234305
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/fontFamilyBackwardsCompatibility.php
@@ -0,0 +1,164 @@
+create_font_family( $legacy_content );
+
+ gutenberg_convert_legacy_font_family_format();
+
+ $font_family = get_post( $font_family_id );
+ $font_faces = $this->get_font_faces( $font_family_id );
+
+ list( $font_face1, $font_face2, $font_face3 ) = $font_faces;
+
+ // Updated font family post.
+ $this->assertSame( 'wp_font_family', $font_family->post_type );
+ $this->assertSame( 'publish', $font_family->post_status );
+
+ $font_family_title = 'Open Sans';
+ $this->assertSame( $font_family_title, $font_family->post_title );
+
+ $font_family_slug = 'open-sans';
+ $this->assertSame( $font_family_slug, $font_family->post_name );
+
+ $font_family_content = wp_json_encode( json_decode( '{"fontFamily":"\'Open Sans\', sans-serif","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans.svg"}', true ) );
+ $this->assertSame( $font_family_content, $font_family->post_content );
+
+ $meta = get_post_meta( $font_family_id, '_gutenberg_legacy_font_family', true );
+ $this->assertSame( $legacy_content, $meta );
+
+ // First font face post.
+ $this->assertSame( 'wp_font_face', $font_face1->post_type );
+ $this->assertSame( $font_family_id, $font_face1->post_parent );
+ $this->assertSame( 'publish', $font_face1->post_status );
+
+ $font_face1_title = 'open sans;normal;400;100%;U+0-10FFFF';
+ $this->assertSame( $font_face1_title, $font_face1->post_title );
+ $this->assertSame( sanitize_title( $font_face1_title ), $font_face1->post_name );
+
+ $font_face1_content = wp_json_encode( json_decode( '{"fontFamily":"Open Sans","fontStyle":"normal","fontWeight":"400","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg","src":"https://fonts.gstatic.com/s/opensans/v35/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4nY1M2xLER.ttf"}' ) );
+ $this->assertSame( $font_face1_content, $font_face1->post_content );
+
+ // With a remote url, file post meta should not be set.
+ $meta = get_post_meta( $font_face1->ID, '_wp_font_face_file', true );
+ $this->assertSame( '', $meta );
+
+ // Second font face post.
+ $this->assertSame( 'wp_font_face', $font_face2->post_type );
+ $this->assertSame( $font_family_id, $font_face2->post_parent );
+ $this->assertSame( 'publish', $font_face2->post_status );
+
+ $font_face2_title = 'open sans;italic;400;100%;U+0-10FFFF';
+ $this->assertSame( $font_face2_title, $font_face2->post_title );
+ $this->assertSame( sanitize_title( $font_face2_title ), $font_face2->post_name );
+
+ $font_face2_content = wp_json_encode( json_decode( '{"fontFamily":"Open Sans","fontStyle":"italic","fontWeight":"400","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-italic.svg","src":"https://fonts.gstatic.com/s/opensans/v35/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVcUwaERZjA.ttf"}' ) );
+ $this->assertSame( $font_face2_content, $font_face2->post_content );
+
+ // With a remote url, file post meta should not be set.
+ $meta = get_post_meta( $font_face2->ID, '_wp_font_face_file', true );
+ $this->assertSame( '', $meta );
+
+ // Third font face post.
+ $this->assertSame( 'wp_font_face', $font_face3->post_type );
+ $this->assertSame( $font_family_id, $font_face3->post_parent );
+ $this->assertSame( 'publish', $font_face3->post_status );
+
+ $font_face3_title = 'open sans;normal;700;100%;U+0-10FFFF';
+ $this->assertSame( $font_face3_title, $font_face3->post_title );
+ $this->assertSame( sanitize_title( $font_face3_title ), $font_face3->post_name );
+
+ $font_face3_content = wp_json_encode( json_decode( '{"fontFamily":"Open Sans","fontStyle":"normal","fontWeight":"700","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-700-normal.svg","src":"https://fonts.gstatic.com/s/opensans/v35/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4nY1M2xLER.ttf"}' ) );
+ $this->assertSame( $font_face3_content, $font_face3->post_content );
+
+ // With a remote url, file post meta should not be set.
+ $meta = get_post_meta( $font_face3->ID, '_wp_font_face_file', true );
+ $this->assertSame( '', $meta );
+
+ wp_delete_post( $font_family_id, true );
+ wp_delete_post( $font_face1->ID, true );
+ wp_delete_post( $font_face2->ID, true );
+ wp_delete_post( $font_face3->ID, true );
+ }
+
+ public function test_font_faces_with_local_src() {
+ $legacy_content = '{"fontFace":[{"fontFamily":"Open Sans","fontStyle":"normal","fontWeight":"400","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg","src":"' . site_url() . '/wp-content/fonts/open-sans_normal_400.ttf"}],"fontFamily":"\'Open Sans\', sans-serif","name":"Open Sans","preview":"https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans.svg","slug":"open-sans"}';
+
+ $font_family_id = $this->create_font_family( $legacy_content );
+
+ gutenberg_convert_legacy_font_family_format();
+
+ $font_faces = $this->get_font_faces( $font_family_id );
+ $this->assertCount( 1, $font_faces );
+ $font_face = reset( $font_faces );
+
+ // Check that file meta is present.
+ $file_path = 'open-sans_normal_400.ttf';
+ $meta = get_post_meta( $font_face->ID, '_wp_font_face_file', true );
+ $this->assertSame( $file_path, $meta );
+
+ wp_delete_post( $font_family_id, true );
+ wp_delete_post( $font_face->ID, true );
+ }
+
+ public function test_migration_only_runs_once() {
+ $legacy_content = '{"fontFace":[],"fontFamily":"\'Open Sans\', sans-serif","name":"Open Sans","preview":"","slug":"open-sans"}';
+
+ // Simulate that the migration has already run.
+ update_option( 'gutenberg_font_family_format_converted', true );
+
+ $font_family_id = $this->create_font_family( $legacy_content );
+
+ gutenberg_convert_legacy_font_family_format();
+
+ // Meta with backup content will not be present if migration isn't triggered.
+ $meta = get_post_meta( $font_family_id, '_gutenberg_legacy_font_family', true );
+ $this->assertSame( '', $meta );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ protected function create_font_family( $content ) {
+ return wp_insert_post(
+ array(
+ 'post_type' => 'wp_font_family',
+ 'post_status' => 'publish',
+ 'post_title' => 'Open Sans',
+ 'post_name' => 'open-sans',
+ 'post_content' => $content,
+ )
+ );
+ }
+
+ protected function get_font_faces( $font_family_id ) {
+ return get_posts(
+ array(
+ 'post_parent' => $font_family_id,
+ 'post_type' => 'wp_font_face',
+ 'order' => 'ASC',
+ 'orderby' => 'id',
+ )
+ );
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/fontLibraryHooks.php b/phpunit/tests/fonts/font-library/fontLibraryHooks.php
new file mode 100644
index 00000000000000..2c471e2a9759c6
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/fontLibraryHooks.php
@@ -0,0 +1,85 @@
+post->create(
+ array(
+ 'post_type' => 'wp_font_family',
+ )
+ );
+ $font_face_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'wp_font_face',
+ 'post_parent' => $font_family_id,
+ )
+ );
+ $other_font_family_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'wp_font_family',
+ )
+ );
+ $other_font_face_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'wp_font_face',
+ 'post_parent' => $other_font_family_id,
+ )
+ );
+
+ wp_delete_post( $font_family_id, true );
+
+ $this->assertNull( get_post( $font_face_id ) );
+ $this->assertNotNull( get_post( $other_font_face_id ) );
+ }
+
+ public function test_deleting_font_faces_deletes_associated_font_files() {
+ list( $font_face_id, $font_path ) = $this->create_font_face_with_file( 'OpenSans-Regular.woff2' );
+ list( , $other_font_path ) = $this->create_font_face_with_file( 'OpenSans-Regular.ttf' );
+
+ wp_delete_post( $font_face_id, true );
+
+ $this->assertFalse( file_exists( $font_path ) );
+ $this->assertTrue( file_exists( $other_font_path ) );
+ }
+
+ protected function create_font_face_with_file( $filename ) {
+ $font_face_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'wp_font_face',
+ )
+ );
+
+ $font_file = $this->upload_font_file( $filename );
+
+ // Make sure the font file uploaded successfully.
+ $this->assertFalse( $font_file['error'] );
+
+ $font_path = $font_file['file'];
+ $font_filename = basename( $font_path );
+ add_post_meta( $font_face_id, '_wp_font_face_file', $font_filename );
+
+ return array( $font_face_id, $font_path );
+ }
+
+ protected function upload_font_file( $font_filename ) {
+ // @core-merge Use `DIR_TESTDATA` instead of `GUTENBERG_DIR_TESTDATA`.
+ $font_file_path = GUTENBERG_DIR_TESTDATA . 'fonts/' . $font_filename;
+
+ add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );
+ add_filter( 'upload_dir', 'wp_get_font_dir' );
+ $font_file = wp_upload_bits(
+ $font_filename,
+ null,
+ file_get_contents( $font_file_path )
+ );
+ remove_filter( 'upload_dir', 'wp_get_font_dir' );
+ remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) );
+
+ return $font_file;
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php
index 380226ee8af8a3..8e9cf7bca08e5e 100644
--- a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php
+++ b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php
@@ -13,32 +13,50 @@
class Tests_Fonts_WpFontCollection_Construct extends WP_UnitTestCase {
public function test_should_initialize_data() {
- $property = new ReflectionProperty( WP_Font_Collection::class, 'config' );
- $property->setAccessible( true );
+ $slug = new ReflectionProperty( WP_Font_Collection::class, 'slug' );
+ $slug->setAccessible( true );
- $config = array(
+ $name = new ReflectionProperty( WP_Font_Collection::class, 'name' );
+ $name->setAccessible( true );
+
+ $description = new ReflectionProperty( WP_Font_Collection::class, 'description' );
+ $description->setAccessible( true );
+
+ $src = new ReflectionProperty( WP_Font_Collection::class, 'src' );
+ $src->setAccessible( true );
+
+ $config = array(
'slug' => 'my-collection',
'name' => 'My Collection',
'description' => 'My collection description',
'src' => 'my-collection-data.json',
);
- $font_collection = new WP_Font_Collection( $config );
+ $collection = new WP_Font_Collection( $config );
- $actual = $property->getValue( $font_collection );
- $property->setAccessible( false );
+ $actual_slug = $slug->getValue( $collection );
+ $this->assertSame( 'my-collection', $actual_slug, 'Provided slug and initialized slug should match.' );
+ $slug->setAccessible( false );
- $this->assertSame( $config, $actual );
+ $actual_name = $name->getValue( $collection );
+ $this->assertSame( 'My Collection', $actual_name, 'Provided name and initialized name should match.' );
+ $name->setAccessible( false );
+
+ $actual_description = $description->getValue( $collection );
+ $this->assertSame( 'My collection description', $actual_description, 'Provided description and initialized description should match.' );
+ $description->setAccessible( false );
+
+ $actual_src = $src->getValue( $collection );
+ $this->assertSame( 'my-collection-data.json', $actual_src, 'Provided src and initialized src should match.' );
+ $src->setAccessible( false );
}
/**
- * @dataProvider data_should_throw_exception
+ * @dataProvider data_should_do_ti_wrong
*
* @param mixed $config Config of the font collection.
- * @param string $expected_exception_message Expected exception message.
*/
- public function test_should_throw_exception( $config, $expected_exception_message ) {
- $this->expectException( 'Exception' );
- $this->expectExceptionMessage( $expected_exception_message );
+ public function test_should_do_ti_wrong( $config ) {
+ $this->setExpectedIncorrectUsage( 'WP_Font_Collection::is_config_valid' );
new WP_Font_Collection( $config );
}
@@ -47,7 +65,7 @@ public function test_should_throw_exception( $config, $expected_exception_messag
*
* @return array
*/
- public function data_should_throw_exception() {
+ public function data_should_do_ti_wrong() {
return array(
'no id' => array(
array(
@@ -55,27 +73,22 @@ public function data_should_throw_exception() {
'description' => 'My collection description',
'src' => 'my-collection-data.json',
),
- 'Font Collection config slug is required as a non-empty string.',
),
'no config' => array(
'',
- 'Font Collection config options is required as a non-empty array.',
),
'empty array' => array(
array(),
- 'Font Collection config options is required as a non-empty array.',
),
'boolean instead of config array' => array(
false,
- 'Font Collection config options is required as a non-empty array.',
),
'null instead of config array' => array(
null,
- 'Font Collection config options is required as a non-empty array.',
),
'missing src' => array(
@@ -84,9 +97,7 @@ public function data_should_throw_exception() {
'name' => 'My Collection',
'description' => 'My collection description',
),
- 'Font Collection config "src" option OR "data" option is required.',
),
-
);
}
}
diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php
index 5f1f082297d418..393de7d22614da 100644
--- a/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php
+++ b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php
@@ -32,7 +32,7 @@ public function data_should_get_config() {
file_put_contents( $mock_file, '{"this is mock data":true}' );
return array(
- 'with a file' => array(
+ 'with a file' => array(
'config' => array(
'slug' => 'my-collection',
'name' => 'My Collection',
@@ -45,7 +45,7 @@ public function data_should_get_config() {
'description' => 'My collection description',
),
),
- 'with a url' => array(
+ 'with a url' => array(
'config' => array(
'slug' => 'my-collection-with-url',
'name' => 'My Collection with URL',
@@ -58,12 +58,12 @@ public function data_should_get_config() {
'description' => 'My collection description',
),
),
- 'with data' => array(
+ 'with font_families' => array(
'config' => array(
- 'slug' => 'my-collection',
- 'name' => 'My Collection',
- 'description' => 'My collection description',
- 'data' => array( 'this is mock data' => true ),
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'font_families' => array( array() ),
),
'expected_data' => array(
'slug' => 'my-collection',
diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php b/phpunit/tests/fonts/font-library/wpFontCollection/getContent.php
similarity index 52%
rename from phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php
rename to phpunit/tests/fonts/font-library/wpFontCollection/getContent.php
index 885b0a0b9036cb..ab0e87cde000e7 100644
--- a/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php
+++ b/phpunit/tests/fonts/font-library/wpFontCollection/getContent.php
@@ -1,6 +1,6 @@
'mock',
- 'categories' => 'mock',
+ 'font_families' => array( 'mock' ),
+ 'categories' => array( 'mock' ),
);
return array(
@@ -47,14 +47,14 @@ public function mock_request( $preempt, $args, $url ) {
}
/**
- * @dataProvider data_should_get_config_and_data
+ * @dataProvider data_should_get_content
*
* @param array $config Font collection config options.
- * @param array $expected_data Expected data.
+ * @param array $expected_data Expected output data.
*/
- public function test_should_get_config_and_data( $config, $expected_data ) {
+ public function test_should_get_content( $config, $expected_data ) {
$collection = new WP_Font_Collection( $config );
- $this->assertSame( $expected_data, $collection->get_config_and_data() );
+ $this->assertSame( $expected_data, $collection->get_content() );
}
/**
@@ -62,12 +62,12 @@ public function test_should_get_config_and_data( $config, $expected_data ) {
*
* @return array[]
*/
- public function data_should_get_config_and_data() {
+ public function data_should_get_content() {
$mock_file = wp_tempnam( 'my-collection-data-' );
- file_put_contents( $mock_file, '{"this is mock data":true}' );
+ file_put_contents( $mock_file, '{"font_families":[ "mock" ], "categories":[ "mock" ] }' );
return array(
- 'with a file' => array(
+ 'with a file' => array(
'config' => array(
'slug' => 'my-collection',
'name' => 'My Collection',
@@ -75,13 +75,11 @@ public function data_should_get_config_and_data() {
'src' => $mock_file,
),
'expected_data' => array(
- 'slug' => 'my-collection',
- 'name' => 'My Collection',
- 'description' => 'My collection description',
- 'data' => array( 'this is mock data' => true ),
+ 'font_families' => array( 'mock' ),
+ 'categories' => array( 'mock' ),
),
),
- 'with a url' => array(
+ 'with a url' => array(
'config' => array(
'slug' => 'my-collection-with-url',
'name' => 'My Collection with URL',
@@ -89,27 +87,33 @@ public function data_should_get_config_and_data() {
'src' => 'https://localhost/fonts/mock-font-collection.json',
),
'expected_data' => array(
- 'slug' => 'my-collection-with-url',
- 'name' => 'My Collection with URL',
- 'description' => 'My collection description',
- 'data' => array(
- 'fontFamilies' => 'mock',
- 'categories' => 'mock',
- ),
+ 'font_families' => array( 'mock' ),
+ 'categories' => array( 'mock' ),
),
),
- 'with data' => array(
+ 'with font_families and categories' => array(
'config' => array(
- 'slug' => 'my-collection',
- 'name' => 'My Collection',
- 'description' => 'My collection description',
- 'data' => array( 'this is mock data' => true ),
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'font_families' => array( 'mock' ),
+ 'categories' => array( 'mock' ),
),
'expected_data' => array(
- 'slug' => 'my-collection',
- 'name' => 'My Collection',
- 'description' => 'My collection description',
- 'data' => array( 'this is mock data' => true ),
+ 'font_families' => array( 'mock' ),
+ 'categories' => array( 'mock' ),
+ ),
+ ),
+ 'with font_families without categories' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'font_families' => array( 'mock' ),
+ ),
+ 'expected_data' => array(
+ 'font_families' => array( 'mock' ),
+ 'categories' => array(),
),
),
);
diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/isConfigValid.php b/phpunit/tests/fonts/font-library/wpFontCollection/isConfigValid.php
new file mode 100644
index 00000000000000..7cfdfc829ab863
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpFontCollection/isConfigValid.php
@@ -0,0 +1,103 @@
+assertTrue( WP_Font_Collection::is_config_valid( $config ) );
+ }
+
+ public function data_is_config_valid() {
+ return array(
+ 'with src' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'src' => 'my-collection-data.json',
+ ),
+ ),
+ 'with font families' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'font_families' => array( 'mock' ),
+ ),
+ ),
+
+ );
+ }
+
+ /**
+ * @dataProvider data_is_config_valid_should_call_doing_ti_wrong
+ *
+ * @param mixed $config Config of the font collection.
+ */
+ public function test_is_config_valid_should_call_doing_ti_wrong( $config ) {
+ $this->setExpectedIncorrectUsage( 'WP_Font_Collection::is_config_valid', 'Should call _doing_it_wrong if the config is not valid.' );
+ $this->assertFalse( WP_Font_Collection::is_config_valid( $config ), 'Should return false if the config is not valid.' );
+ }
+
+ public function data_is_config_valid_should_call_doing_ti_wrong() {
+ return array(
+ 'with missing slug' => array(
+ 'config' => array(
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'src' => 'my-collection-data.json',
+ ),
+ ),
+ 'with missing name' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'description' => 'My collection description',
+ 'src' => 'my-collection-data.json',
+ ),
+ ),
+ 'with missing src' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ ),
+ ),
+ 'with both src and font_families' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ 'src' => 'my-collection-data.json',
+ 'font_families' => array( 'mock' ),
+ ),
+ ),
+ 'without src or font_families' => array(
+ 'config' => array(
+ 'slug' => 'my-collection',
+ 'name' => 'My Collection',
+ 'description' => 'My collection description',
+ ),
+ ),
+ 'with empty config' => array(
+ 'config' => array(),
+ ),
+ 'without an array' => array(
+ 'config' => 'not an array',
+ ),
+ );
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/__construct.php b/phpunit/tests/fonts/font-library/wpFontFamily/__construct.php
deleted file mode 100644
index 3a1e387c3651b2..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/__construct.php
+++ /dev/null
@@ -1,62 +0,0 @@
-setAccessible( true );
-
- $font_data = array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- );
- $font_family = new WP_Font_Family( $font_data );
-
- $actual = $property->getValue( $font_family );
- $property->setAccessible( false );
-
- $this->assertSame( $font_data, $actual );
- }
-
- /**
- * @dataProvider data_should_throw_exception
- *
- * @param mixed $font_data Data to test.
- */
- public function test_should_throw_exception( $font_data ) {
- $this->expectException( 'Exception' );
- $this->expectExceptionMessage( 'Font family data is missing the slug.' );
-
- new WP_Font_Family( $font_data );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_should_throw_exception() {
- return array(
- 'no slug' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- ),
- ),
- 'empty array' => array( array() ),
- 'boolean instead of array' => array( false ),
- 'null instead of array' => array( null ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/base.php b/phpunit/tests/fonts/font-library/wpFontFamily/base.php
deleted file mode 100644
index 3f6ff153fa12f5..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/base.php
+++ /dev/null
@@ -1,75 +0,0 @@
- array(),
- 'files_data' => array(),
- 'font_filename' => '',
- );
-
- public static function set_up_before_class() {
- parent::set_up_before_class();
-
- static::$fonts_dir = wp_get_font_dir()['path'];
- wp_mkdir_p( static::$fonts_dir );
- }
-
- public function set_up() {
- parent::set_up();
-
- $merriweather_tmp_name = wp_tempnam( 'Merriweather-' );
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $merriweather_tmp_name );
- $this->merriweather = array(
- 'font_data' => array(
- 'name' => 'Merriweather',
- 'slug' => 'merriweather',
- 'fontFamily' => 'Merriweather',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Merriweather',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'merriweather.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => $merriweather_tmp_name,
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'font_filename' => path_join( static::$fonts_dir, 'merriweather_normal_400.ttf' ),
- );
- }
-
- public function tear_down() {
- // Clean up the /fonts directory.
- foreach ( $this->files_in_dir( static::$fonts_dir ) as $file ) {
- @unlink( $file );
- }
-
- parent::tear_down();
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/getData.php b/phpunit/tests/fonts/font-library/wpFontFamily/getData.php
deleted file mode 100644
index 2de88ad3aae375..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/getData.php
+++ /dev/null
@@ -1,94 +0,0 @@
-assertSame( $font_data, $font->get_data() );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_get_data() {
- return array(
- 'with one google font face to be downloaded' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- ),
- 'with one google font face to not be downloaded' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- ),
- 'without font faces' => array(
- array(
- 'name' => 'Arial',
- 'slug' => 'arial',
- 'fontFamily' => 'Arial',
- 'fontFace' => array(),
- ),
- ),
- 'with local files' => array(
- array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files0',
- ),
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files1',
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/getDataAsJson.php b/phpunit/tests/fonts/font-library/wpFontFamily/getDataAsJson.php
deleted file mode 100644
index b18b2d0b4f4b38..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/getDataAsJson.php
+++ /dev/null
@@ -1,67 +0,0 @@
-assertSame( $expected, $font->get_data_as_json() );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_get_data_as_json() {
- return array(
- 'piazzolla' => array(
- 'font_data' => array(
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'src' => 'https://example.com/fonts/piazzolla_italic_400.ttf',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- ),
- ),
- ),
- 'expected' => '{"slug":"piazzolla","fontFamily":"Piazzolla","name":"Piazzolla","fontFace":[{"fontFamily":"Piazzolla","src":"https:\/\/example.com\/fonts\/piazzolla_italic_400.ttf","fontStyle":"italic","fontWeight":"400"}]}',
- ),
- 'piazzolla2' => array(
- 'font_data' => array(
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'src' => 'https://example.com/fonts/piazzolla_italic_400.ttf',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- ),
- ),
- ),
- 'expected' => '{"slug":"piazzolla","fontFamily":"Piazzolla","name":"Piazzolla","fontFace":[{"fontFamily":"Piazzolla","src":"https:\/\/example.com\/fonts\/piazzolla_italic_400.ttf","fontStyle":"italic","fontWeight":"400"}]}',
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/getFontPost.php b/phpunit/tests/fonts/font-library/wpFontFamily/getFontPost.php
deleted file mode 100644
index eb42cc3ee08986..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/getFontPost.php
+++ /dev/null
@@ -1,42 +0,0 @@
- $this->merriweather['font_data']['name'],
- 'post_name' => $this->merriweather['font_data']['slug'],
- 'post_type' => 'wp_font_family',
- 'post_content' => '',
- 'post_status' => 'publish',
- );
- $post_id = wp_insert_post( $post );
- $font = new WP_Font_Family( $this->merriweather['font_data'] );
-
- // Test.
- $actual = $font->get_font_post();
- $this->assertInstanceOf( WP_Post::class, $actual, 'Font post should exist' );
- $this->assertSame( $post_id, $actual->ID, 'Font post ID should match' );
- }
-
- public function test_should_return_null_when_post_does_not_exist() {
- $font = new WP_Font_Family( $this->merriweather['font_data'] );
-
- $this->assertNull( $font->get_font_post() );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/hasFontFaces.php b/phpunit/tests/fonts/font-library/wpFontFamily/hasFontFaces.php
deleted file mode 100644
index 0c153d62aa79dc..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/hasFontFaces.php
+++ /dev/null
@@ -1,78 +0,0 @@
- 'piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- ),
- ),
- );
- $font = new WP_Font_Family( $font_data );
- $this->assertTrue( $font->has_font_faces() );
- }
-
- /**
- * @dataProvider data_should_return_false_when_check_fails
- *
- * @param array $font_data Font family data in theme.json format.
- */
- public function test_should_return_false_when_check_fails( $font_data ) {
- $font = new WP_Font_Family( $font_data );
- $this->assertFalse( $font->has_font_faces() );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_return_false_when_check_fails() {
- return array(
- 'wrong fontFace key' => array(
- array(
- 'slug' => 'piazzolla',
- 'fontFaces' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- ),
- ),
- ),
- ),
- 'without font faces' => array(
- array(
- 'slug' => 'piazzolla',
- ),
- ),
- 'empty array' => array(
- array(
- 'slug' => 'piazzolla',
- 'fontFace' => array(),
- ),
- ),
- 'null' => array(
- array(
- 'slug' => 'piazzolla',
- 'fontFace' => null,
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/install.php b/phpunit/tests/fonts/font-library/wpFontFamily/install.php
deleted file mode 100644
index 79cb6ac3ba2ba4..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/install.php
+++ /dev/null
@@ -1,562 +0,0 @@
-install();
- $this->assertEmpty( $this->files_in_dir( static::$fonts_dir ), 'Font directory should be empty' );
- $this->assertInstanceOf( WP_Post::class, $font->get_font_post(), 'Font post should exist after install' );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_not_download_when_no_fontface() {
- return array(
- 'wrong fontFace key' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFaces' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- ),
- ),
- ),
- ),
- 'without font faces' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- ),
- ),
- 'empty array' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFace' => array(),
- ),
- ),
- 'null' => array(
- array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFace' => null,
- ),
- ),
- );
- }
-
- /**
- * @dataProvider data_should_download_fontfaces
- *
- * @param array $font_data Font family data in theme.json format.
- * @param array $expected Expected font filename(s).
- */
- public function test_should_download_fontfaces_and_create_post( $font_data, array $expected ) {
- // Pre-checks to ensure starting conditions.
- foreach ( $expected as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileDoesNotExist( $font_file, "Font file [{$font_file}] should not exist in the fonts/ directory after installing" );
- }
- $font = new WP_Font_Family( $font_data );
-
- // Test.
- $font->install();
- foreach ( $expected as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileExists( $font_file, "Font file [{$font_file}] should exists in the fonts/ directory after installing" );
- }
- $this->assertInstanceOf( WP_Post::class, $font->get_font_post(), 'Font post should exist after install' );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_download_fontfaces() {
- return array(
- '1 font face to download' => array(
- 'font_data' => array(
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFamily' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- 'expected' => array( 'piazzolla_italic_400.ttf' ),
- ),
- '2 font faces to download' => array(
- 'font_data' => array(
- 'name' => 'Lato',
- 'slug' => 'lato',
- 'fontFamily' => 'Lato',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHvxk6XweuBCY.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHvxk6XweuBCY.ttf',
- ),
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'italic',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/lato/v24/S6u8w4BMUTPHjxswWyWrFCbw7A.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/lato/v24/S6u8w4BMUTPHjxswWyWrFCbw7A.ttf',
- ),
- ),
- ),
- 'expected' => array( 'lato_normal_400.ttf', 'lato_italic_400.ttf' ),
- ),
- );
- }
-
- /**
- * @dataProvider data_should_move_local_fontfaces
- *
- * @param array $font_data Font family data in theme.json format.
- * @param array $files_data Files data in $_FILES format.
- * @param array $expected Expected font filename(s).
- */
- public function test_should_move_local_fontfaces( $font_data, array $files_data, array $expected ) {
- // Set up the temporary files.
- foreach ( $files_data as $file ) {
- if ( 'font/ttf' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- } elseif ( 'font/woff' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/cooper-hewitt.woff', $file['tmp_name'] );
- } elseif ( 'font/woff2' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/DMSans.woff2', $file['tmp_name'] );
- } elseif ( 'application/vnd.ms-opentype' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/gilbert-color.otf', $file['tmp_name'] );
- }
- }
-
- $font = new WP_Font_Family( $font_data );
- $font->install( $files_data );
-
- foreach ( $expected as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileExists( $font_file, "Font file [{$font_file}] should exists in the fonts/ directory after installing" );
- }
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_move_local_fontfaces() {
- return array(
- // ttf font type.
- '1 local font' => array(
- 'font_data' => array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'inter1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'inter_italic_900.ttf' ),
- ),
- '2 local fonts' => array(
- 'font_data' => array(
- 'name' => 'Lato',
- 'slug' => 'lato',
- 'fontFamily' => 'Lato',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files1',
- ),
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files2',
- ),
- ),
- ),
- 'files_data' => array(
- 'files1' => array(
- 'name' => 'lato1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files2' => array(
- 'name' => 'lato2.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'lato_normal_400.ttf', 'lato_normal_500.ttf' ),
- ),
- // woff font type.
- 'woff local font' => array(
- 'font_data' => array(
- 'name' => 'Cooper Hewitt',
- 'slug' => 'cooper-hewitt',
- 'fontFamily' => 'Cooper Hewitt',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Cooper Hewitt',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'cooper-hewitt.woff',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Cooper-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'cooper-hewitt_italic_900.woff' ),
- ),
- // woff2 font type.
- 'woff2 local font' => array(
- 'font_data' => array(
- 'name' => 'DM Sans',
- 'slug' => 'dm-sans',
- 'fontFamily' => 'DM Sans',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'DM Sans',
- 'fontStyle' => 'regular',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'DMSans.woff2',
- 'type' => 'font/woff2',
- 'tmp_name' => wp_tempnam( 'DMSans-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'dm-sans_regular_500.woff2' ),
- ),
- // otf font type.
- 'otf local font' => array(
- 'font_data' => array(
- 'name' => 'Gilbert Color',
- 'slug' => 'gilbert-color',
- 'fontFamily' => 'Gilbert Color',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Gilbert Color',
- 'fontStyle' => 'regular',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'gilbert-color.otf',
- 'type' => 'application/vnd.ms-opentype',
- 'tmp_name' => wp_tempnam( 'Gilbert-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'gilbert-color_regular_500.otf' ),
- ),
- );
- }
-
- /**
- * @dataProvider data_should_not_install_duplicate_fontfaces
- *
- * @param array $font_data Font family data in theme.json format.
- * @param array $files_data Files data in $_FILES format.
- * @param array $expected Expected font filename(s).
- */
- public function test_should_not_install_duplicate_fontfaces( $font_data, array $files_data, array $expected ) {
- // Set up the temporary files.
- foreach ( $files_data as $file ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- }
-
- $font = new WP_Font_Family( $font_data );
- $font->install( $files_data );
-
- $this->assertCount( count( $expected ), $this->files_in_dir( static::$fonts_dir ), 'Font directory should contain the same number of files as expected' );
-
- foreach ( $expected as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileExists( $font_file, "Font file [{$font_file}] should exists in the fonts/ directory after installing" );
- }
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_not_install_duplicate_fontfaces() {
- return array(
- 'single unique font face' => array(
- 'font_data' => array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files0',
- ),
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files1',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'inter1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files1' => array(
- 'name' => 'inter1.woff',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'inter_italic_900.ttf' ),
- ),
- 'multiple unique font faces' => array(
- 'font_data' => array(
- 'name' => 'Lato',
- 'slug' => 'lato',
- 'fontFamily' => 'Lato',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files0',
- ),
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files1',
- ),
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files2',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'lato1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files1' => array(
- 'name' => 'lato2.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files2' => array(
- 'name' => 'lato2.woff',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected' => array( 'lato_normal_400.ttf', 'lato_normal_500.ttf' ),
- ),
- );
- }
-
- public function test_should_overwrite_fontface_with_different_extension() {
- $font_data_initial = array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files0',
- ),
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files1',
- ),
- ),
- );
- $files_data_initial = array(
- 'files0' => array(
- 'name' => 'inter1.ttf',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files1' => array(
- 'name' => 'inter1.woff',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- );
- $font_data_overwrite = array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files0',
- ),
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files1',
- ),
- ),
- );
- $files_data_overwrite = array(
- 'files0' => array(
- 'name' => 'inter1.woff',
- 'type' => 'font/woff',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files1' => array(
- 'name' => 'inter1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- );
-
- $expected = array( 'inter_italic_500.woff', 'inter_italic_900.ttf' );
-
- // Set up the temporary files.
- foreach ( $files_data_initial as $file ) {
- if ( 'font/ttf' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- } elseif ( 'font/woff' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/cooper-hewitt.woff', $file['tmp_name'] );
- }
- }
- foreach ( $files_data_overwrite as $file ) {
- if ( 'font/ttf' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- } elseif ( 'font/woff' === $file['type'] ) {
- copy( __DIR__ . '/../../../data/fonts/cooper-hewitt.woff', $file['tmp_name'] );
- }
- }
-
- $font = new WP_Font_Family( $font_data_initial );
- $font->install( $files_data_initial );
-
- $font = new WP_Font_Family( $font_data_overwrite );
- $font->install( $files_data_overwrite );
-
- $this->assertCount( count( $expected ), $this->files_in_dir( static::$fonts_dir ), 'Font directory should contain the same number of files as expected' );
-
- foreach ( $expected as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileExists( $font_file, "Font file [{$font_file}] should exists in the fonts/ directory after installing" );
- }
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php b/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php
deleted file mode 100644
index c878dd00fdb5ca..00000000000000
--- a/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php
+++ /dev/null
@@ -1,194 +0,0 @@
-merriweather['font_data'] );
-
- // Test.
- $actual = $font->uninstall();
- $this->assertWPError( $actual, 'WP_Error should have been returned' );
- $this->assertSame(
- array( 'font_family_not_found' => array( 'The font family could not be found.' ) ),
- $actual->errors,
- 'WP_Error should have "fonts_must_have_same_slug" error'
- );
- }
-
- /**
- * @dataProvider data_should_return_error_when_not_able_to_uninstall
- *
- * @param string $failure_to_mock The filter name to mock the failure.
- */
- public function test_should_return_error_when_not_able_to_uninstall( $failure_to_mock ) {
- // Set up the font.
- add_filter( $failure_to_mock, '__return_empty_string' );
- $font = new WP_Font_Family( $this->merriweather['font_data'] );
- $font->install( $this->merriweather['files_data'] );
-
- // Test.
- $actual = $font->uninstall();
- $this->assertWPError( $actual, 'WP_Error should be returned' );
- $this->assertSame(
- array( 'font_family_not_deleted' => array( 'The font family could not be deleted.' ) ),
- $actual->errors,
- 'WP_Error should have "font_family_not_deleted" error'
- );
- }
-
- /**
- * Data provider.
- *
- * @return string[][]
- */
- public function data_should_return_error_when_not_able_to_uninstall() {
- return array(
- 'When delete file fails' => array( 'wp_delete_file' ),
- 'when delete post fails' => array( 'pre_delete_post' ),
- );
- }
-
- /**
- * @dataProvider data_should_uninstall
- *
- * @param array $font_data Font family data in theme.json format.
- * @param array $files_data Files data in $_FILES format.
- */
- public function test_should_uninstall( $font_data, array $files_data ) {
- // Set up.
- foreach ( $files_data as $file ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- }
- $font = new WP_Font_Family( $font_data );
- $font->install( $files_data );
-
- // Pre-checks to ensure the starting point is as expected.
- $this->assertInstanceOf( WP_Post::class, $font->get_font_post(), 'Font post should exist' );
- $this->assertNotEmpty( $this->files_in_dir( static::$fonts_dir ), 'Fonts should be installed' );
-
- // Uninstall.
- $this->assertTrue( $font->uninstall() );
-
- // Test the post and font file(s) were uninstalled.
- $this->assertNull( $font->get_font_post(), 'Font post should be deleted after uninstall' );
- $this->assertEmpty( $this->files_in_dir( static::$fonts_dir ), 'Fonts should be uninstalled' );
- }
-
- /**
- * @dataProvider data_should_uninstall
- *
- * @param array $font_data Font family data in theme.json format.
- * @param array $files_data Files data in $_FILES format.
- * @param array $files_to_uninstall Files to uninstall.
- */
- public function test_should_uninstall_only_its_font_family( $font_data, array $files_data, array $files_to_uninstall ) {
- // Set up a different font family instance. This font family should not be uninstalled.
- $merriweather = new WP_Font_Family( $this->merriweather['font_data'] );
- $merriweather->install( $this->merriweather['files_data'] );
-
- // Set up the font family to be uninstalled.
- foreach ( $files_data as $file ) {
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $file['tmp_name'] );
- }
- $font = new WP_Font_Family( $font_data );
- $font->install( $files_data );
-
- $this->assertTrue( $font->uninstall() );
-
- // Check that the files were uninstalled.
- foreach ( $files_to_uninstall as $font_file ) {
- $font_file = path_join( static::$fonts_dir, $font_file );
- $this->assertFileDoesNotExist( $font_file, "Font file [{$font_file}] should not exists in the uploads/fonts/ directory after uninstalling" );
- }
- // Check that the Merriweather file was not uninstalled.
- $this->assertFileExists( $this->merriweather['font_filename'], 'The other font family [Merriweather] should not have been uninstalled.' );
- }
-
- /**
- * Data provider.
- *
- * @return array[]
- */
- public function data_should_uninstall() {
- return array(
- '1 local font' => array(
- 'font_data' => array(
- 'name' => 'Inter',
- 'slug' => 'inter',
- 'fontFamily' => 'Inter',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Inter',
- 'fontStyle' => 'italic',
- 'fontWeight' => '900',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files_data' => array(
- 'files0' => array(
- 'name' => 'inter1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Inter-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'files_to_uninstall' => array( 'inter_italic_900.ttf' ),
- ),
- '2 local fonts' => array(
- 'font_data' => array(
- 'name' => 'Lato',
- 'slug' => 'lato',
- 'fontFamily' => 'Lato',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files1',
- ),
- array(
- 'fontFamily' => 'Lato',
- 'fontStyle' => 'normal',
- 'fontWeight' => '500',
- 'uploadedFile' => 'files2',
- ),
- ),
- ),
- 'files_data' => array(
- 'files1' => array(
- 'name' => 'lato1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- 'files2' => array(
- 'name' => 'lato2.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => wp_tempnam( 'Lato-' ),
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'files_to_uninstall' => array( 'lato_normal_400.ttf', 'lato_normal_500.ttf' ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpFontFamilyUtils/formatFontFamily.php b/phpunit/tests/fonts/font-library/wpFontFamilyUtils/formatFontFamily.php
index 4f247c5219febb..19987010d80a7e 100644
--- a/phpunit/tests/fonts/font-library/wpFontFamilyUtils/formatFontFamily.php
+++ b/phpunit/tests/fonts/font-library/wpFontFamilyUtils/formatFontFamily.php
@@ -36,11 +36,11 @@ public function data_should_format_font_family() {
return array(
'data_families_with_spaces_and_numbers' => array(
'font_family' => 'Rock 3D , Open Sans,serif',
- 'expected' => "'Rock 3D', 'Open Sans', serif",
+ 'expected' => '"Rock 3D", "Open Sans", serif',
),
'data_single_font_family' => array(
'font_family' => 'Rock 3D',
- 'expected' => "'Rock 3D'",
+ 'expected' => '"Rock 3D"',
),
'data_no_spaces' => array(
'font_family' => 'Rock3D',
@@ -48,7 +48,7 @@ public function data_should_format_font_family() {
),
'data_many_spaces_and_existing_quotes' => array(
'font_family' => 'Rock 3D serif, serif,sans-serif, "Open Sans"',
- 'expected' => "'Rock 3D serif', serif, sans-serif, \"Open Sans\"",
+ 'expected' => '"Rock 3D serif", serif, sans-serif, "Open Sans"',
),
'data_empty_family' => array(
'font_family' => ' ',
diff --git a/phpunit/tests/fonts/font-library/wpFontFamilyUtils/getFontFaceSlug.php b/phpunit/tests/fonts/font-library/wpFontFamilyUtils/getFontFaceSlug.php
new file mode 100644
index 00000000000000..1f87d0d2fd5a11
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpFontFamilyUtils/getFontFaceSlug.php
@@ -0,0 +1,81 @@
+assertSame( $expected_slug, $slug );
+ }
+
+ public function data_get_font_face_slug_normalizes_values() {
+ return array(
+ 'Sets defaults' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans',
+ ),
+ 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Converts normal weight to 400' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans',
+ 'fontWeight' => 'normal',
+ ),
+ 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Converts bold weight to 700' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans',
+ 'fontWeight' => 'bold',
+ ),
+ 'expected_slug' => 'open sans;normal;700;100%;U+0-10FFFF',
+ ),
+ 'Converts normal font-stretch to 100%' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans',
+ 'fontStretch' => 'normal',
+ ),
+ 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Removes double quotes from fontFamilies' => array(
+ 'settings' => array(
+ 'fontFamily' => '"Open Sans"',
+ ),
+ 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Removes single quotes from fontFamilies' => array(
+ 'settings' => array(
+ 'fontFamily' => "'Open Sans'",
+ ),
+ 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Removes spaces between comma separated font families' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans, serif',
+ ),
+ 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Removes tabs between comma separated font families' => array(
+ 'settings' => array(
+ 'fontFamily' => "Open Sans,\tserif",
+ ),
+ 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
+ ),
+ 'Removes new lines between comma separated font families' => array(
+ 'settings' => array(
+ 'fontFamily' => "Open Sans,\nserif",
+ ),
+ 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
+ ),
+ );
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php
index a7ea2870957e9d..b06ae3c8d53548 100644
--- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php
+++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php
@@ -29,9 +29,9 @@ public function test_should_return_error_if_slug_is_missing() {
'description' => 'My Collection Description',
'src' => 'my-collection-data.json',
);
- $this->expectException( 'Exception' );
- $this->expectExceptionMessage( 'Font Collection config slug is required as a non-empty string.' );
- WP_Font_Library::register_font_collection( $config );
+ $this->setExpectedIncorrectUsage( 'WP_Font_Collection::is_config_valid' );
+ $collection = WP_Font_Library::register_font_collection( $config );
+ $this->assertWPError( $collection, 'A WP_Error should be returned.' );
}
public function test_should_return_error_if_name_is_missing() {
@@ -40,16 +40,16 @@ public function test_should_return_error_if_name_is_missing() {
'description' => 'My Collection Description',
'src' => 'my-collection-data.json',
);
- $this->expectException( 'Exception' );
- $this->expectExceptionMessage( 'Font Collection config name is required as a non-empty string.' );
- WP_Font_Library::register_font_collection( $config );
+ $this->setExpectedIncorrectUsage( 'WP_Font_Collection::is_config_valid' );
+ $collection = WP_Font_Library::register_font_collection( $config );
+ $this->assertWPError( $collection, 'A WP_Error should be returned.' );
}
public function test_should_return_error_if_config_is_empty() {
$config = array();
- $this->expectException( 'Exception' );
- $this->expectExceptionMessage( 'Font Collection config options is required as a non-empty array.' );
- WP_Font_Library::register_font_collection( $config );
+ $this->setExpectedIncorrectUsage( 'WP_Font_Collection::is_config_valid' );
+ $collection = WP_Font_Library::register_font_collection( $config );
+ $this->assertWPError( $collection, 'A WP_Error should be returned.' );
}
public function test_should_return_error_if_slug_is_repeated() {
diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php
new file mode 100644
index 00000000000000..164f88f3f7b4b2
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php
@@ -0,0 +1,163 @@
+user->create(
+ array(
+ 'role' => 'administrator',
+ )
+ );
+ self::$editor_id = $factory->user->create(
+ array(
+ 'role' => 'editor',
+ )
+ );
+ $mock_file = wp_tempnam( 'my-collection-data-' );
+ file_put_contents( $mock_file, '{"font_families": [ "mock" ], "categories": [ "mock" ] }' );
+
+ wp_register_font_collection(
+ array(
+ 'name' => 'My Collection',
+ 'slug' => 'mock-col-slug',
+ 'src' => $mock_file,
+ )
+ );
+ }
+
+ public static function wpTearDownAfterClass() {
+ self::delete_user( self::$admin_id );
+ self::delete_user( self::$editor_id );
+ wp_unregister_font_collection( 'mock-col-slug' );
+ }
+
+
+ /**
+ * @covers WP_REST_Font_Collections_Controller::register_routes
+ */
+ public function test_register_routes() {
+ $routes = rest_get_server()->get_routes();
+ $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'Rest server has not the collections path initialized.' );
+ $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' );
+
+ $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'Rest server has not the GET method for collections intialized.' );
+ $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' );
+ }
+
+ /**
+ * @covers WP_REST_Font_Collections_Controller::get_items
+ */
+ public function test_get_items() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' );
+ $response = rest_get_server()->dispatch( $request );
+ $content = $response->get_data();
+ $this->assertIsArray( $content );
+ $this->assertEquals( 200, $response->get_status() );
+ }
+
+ /**
+ * @covers WP_REST_Font_Collections_Controller::get_item
+ */
+ public function test_get_item() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertEquals( 200, $response->get_status(), 'Response code is not 200' );
+
+ $response_data = $response->get_data();
+ $this->assertArrayHasKey( 'name', $response_data, 'Response data does not have the name key.' );
+ $this->assertArrayHasKey( 'slug', $response_data, 'Response data does not have the slug key.' );
+ $this->assertArrayHasKey( 'description', $response_data, 'Response data does not have the description key.' );
+ $this->assertArrayHasKey( 'font_families', $response_data, 'Response data does not have the font_families key.' );
+ $this->assertArrayHasKey( 'categories', $response_data, 'Response data does not have the categories key.' );
+
+ $this->assertIsString( $response_data['name'], 'name is not a string.' );
+ $this->assertIsString( $response_data['slug'], 'slug is not a string.' );
+ $this->assertIsString( $response_data['description'], 'description is not a string.' );
+
+ $this->assertIsArray( $response_data['font_families'], 'font_families is not an array.' );
+ $this->assertIsArray( $response_data['categories'], 'categories is not an array.' );
+ }
+
+ /**
+ * @covers WP_REST_Font_Collections_Controller::get_item
+ */
+ public function test_get_item_invalid_slug() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'font_collection_not_found', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Collections_Controller::get_item
+ */
+ public function test_get_item_invalid_id_permission() {
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' );
+
+ wp_set_current_user( 0 );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'Response code should be 401 for non-authenticated users.' );
+
+ wp_set_current_user( self::$editor_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'Response code should be 403 for users without the right permissions.' );
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_context_param() {
+ // Controller does not use get_context_param().
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_create_item() {
+ // Controller does not use test_create_item().
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_update_item() {
+ // Controller does not use test_update_item().
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_delete_item() {
+ // Controller does not use test_delete_item().
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_prepare_item() {
+ // Controller does not use test_prepare_item().
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_get_item_schema() {
+ // Controller does not use test_get_item_schema().
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/base.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/base.php
deleted file mode 100644
index 2469d71dc79ce8..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/base.php
+++ /dev/null
@@ -1,42 +0,0 @@
-factory->user->create(
- array(
- 'role' => 'administrator',
- )
- );
- wp_set_current_user( $admin_id );
- }
-
- /**
- * Tear down each test method.
- */
- public function tear_down() {
- parent::tear_down();
-
- // Reset $collections static property of WP_Font_Library class.
- $reflection = new ReflectionClass( 'WP_Font_Library' );
- $property = $reflection->getProperty( 'collections' );
- $property->setAccessible( true );
- $property->setValue( null, array() );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php
deleted file mode 100644
index c9d003389997b4..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php
+++ /dev/null
@@ -1,126 +0,0 @@
- 'one-collection',
- 'name' => 'One Font Collection',
- 'description' => 'Demo about how to a font collection to your WordPress Font Library.',
- 'src' => $mock_file,
- );
- wp_register_font_collection( $config_with_file );
-
- $config_with_url = array(
- 'slug' => 'collection-with-url',
- 'name' => 'Another Font Collection',
- 'description' => 'Demo about how to a font collection to your WordPress Font Library.',
- 'src' => 'https://wordpress.org/fonts/mock-font-collection.json',
- );
-
- wp_register_font_collection( $config_with_url );
-
- $config_with_non_existing_file = array(
- 'slug' => 'collection-with-non-existing-file',
- 'name' => 'Another Font Collection',
- 'description' => 'Demo about how to a font collection to your WordPress Font Library.',
- 'src' => '/home/non-existing-file.json',
- );
-
- wp_register_font_collection( $config_with_non_existing_file );
-
- $config_with_non_existing_url = array(
- 'slug' => 'collection-with-non-existing-url',
- 'name' => 'Another Font Collection',
- 'description' => 'Demo about how to a font collection to your WordPress Font Library.',
- 'src' => 'https://non-existing-url-1234x.com.ar/fake-path/missing-file.json',
- );
-
- wp_register_font_collection( $config_with_non_existing_url );
- }
-
- public function mock_request( $preempt, $args, $url ) {
- // Check if it's the URL you want to mock.
- if ( 'https://wordpress.org/fonts/mock-font-collection.json' === $url ) {
-
- // Mock the response body.
- $mock_collection_data = array(
- 'fontFamilies' => 'mock',
- 'categories' => 'mock',
- );
-
- return array(
- 'body' => json_encode( $mock_collection_data ),
- 'response' => array(
- 'code' => 200,
- ),
- );
- }
-
- // For any other URL, return false which ensures the request is made as usual (or you can return other mock data).
- return false;
- }
-
- public function tear_down() {
- // Remove the mock to not affect other tests.
- remove_filter( 'pre_http_request', array( $this, 'mock_request' ) );
-
- parent::tear_down();
- }
-
- public function test_get_font_collection_from_file() {
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/one-collection' );
- $response = rest_get_server()->dispatch( $request );
- $data = $response->get_data();
- $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
- $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' );
- $this->assertSame( array( 'this is mock data' => true ), $data['data'], 'The response data does not have the expected file data.' );
- }
-
- public function test_get_font_collection_from_url() {
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-url' );
- $response = rest_get_server()->dispatch( $request );
- $data = $response->get_data();
- $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
- $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' );
- }
-
- public function test_get_non_existing_collection_should_return_404() {
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection-id' );
- $response = rest_get_server()->dispatch( $request );
- $this->assertSame( 404, $response->get_status() );
- }
-
- public function test_get_non_existing_file_should_return_500() {
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-non-existing-file' );
- $response = rest_get_server()->dispatch( $request );
- $this->assertSame( 500, $response->get_status() );
- }
-
- public function test_get_non_existing_url_should_return_500() {
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-non-existing-url' );
- $response = rest_get_server()->dispatch( $request );
- $this->assertSame( 500, $response->get_status() );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php
deleted file mode 100644
index 0a8d24e8f392ba..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php
+++ /dev/null
@@ -1,45 +0,0 @@
-dispatch( $request );
- $this->assertSame( 200, $response->get_status() );
- $this->assertSame( array(), $response->get_data() );
- }
-
- public function test_get_font_collections() {
- // Mock font collection data file.
- $mock_file = wp_tempnam( 'my-collection-data-' );
- file_put_contents( $mock_file, '{"this is mock data":true}' );
-
- // Add a font collection.
- $config = array(
- 'slug' => 'my-font-collection',
- 'name' => 'My Font Collection',
- 'description' => 'Demo about how to a font collection to your WordPress Font Library.',
- 'src' => $mock_file,
- );
- wp_register_font_collection( $config );
-
- $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' );
- $response = rest_get_server()->dispatch( $request );
- $data = $response->get_data();
- $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
- $this->assertCount( 1, $data, 'The response data is not an array with one element.' );
- $this->assertArrayHasKey( 'slug', $data[0], 'The response data does not have the key with the collection slug.' );
- $this->assertArrayHasKey( 'name', $data[0], 'The response data does not have the key with the collection name.' );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php
deleted file mode 100644
index fb100a400fb4cf..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php
+++ /dev/null
@@ -1,24 +0,0 @@
-get_routes();
- $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'Rest server has not the collections path initialized.' );
- $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' );
-
- $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'Rest server has not the GET method for collections intialized.' );
- $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php
new file mode 100644
index 00000000000000..1904a17228bdce
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php
@@ -0,0 +1,887 @@
+ '"Open Sans"',
+ 'fontWeight' => '400',
+ 'fontStyle' => 'normal',
+ 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf',
+ );
+
+ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
+ self::$font_family_id = WP_REST_Font_Families_Controller_Test::create_font_family_post();
+ self::$other_font_family_id = WP_REST_Font_Families_Controller_Test::create_font_family_post();
+
+ self::$font_face_id1 = self::create_font_face_post(
+ self::$font_family_id,
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '400',
+ 'fontStyle' => 'normal',
+ 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ),
+ )
+ );
+ self::$font_face_id2 = self::create_font_face_post(
+ self::$font_family_id,
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '900',
+ 'fontStyle' => 'normal',
+ 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ),
+ )
+ );
+
+ self::$admin_id = $factory->user->create(
+ array(
+ 'role' => 'administrator',
+ )
+ );
+ self::$editor_id = $factory->user->create(
+ array(
+ 'role' => 'editor',
+ )
+ );
+ }
+
+ public static function wpTearDownAfterClass() {
+ self::delete_user( self::$admin_id );
+ self::delete_user( self::$editor_id );
+ }
+
+ public static function create_font_face_post( $parent_id, $settings = array() ) {
+ $settings = array_merge( self::$default_settings, $settings );
+ $title = WP_Font_Family_Utils::get_font_face_slug( $settings );
+ return self::factory()->post->create(
+ wp_slash(
+ array(
+ 'post_type' => 'wp_font_face',
+ 'post_status' => 'publish',
+ 'post_title' => $title,
+ 'post_name' => sanitize_title( $title ),
+ 'post_content' => wp_json_encode( $settings ),
+ 'post_parent' => $parent_id,
+ )
+ )
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::register_routes
+ */
+ public function test_register_routes() {
+ $routes = rest_get_server()->get_routes();
+ $this->assertArrayHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/font-faces',
+ $routes,
+ 'Font faces collection for the given font family does not exist'
+ );
+ $this->assertCount(
+ 2,
+ $routes['/wp/v2/font-families/(?P[\d]+)/font-faces'],
+ 'Font faces collection for the given font family does not have exactly two elements'
+ );
+ $this->assertArrayHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)',
+ $routes,
+ 'Single font face route for the given font family does not exist'
+ );
+ $this->assertCount(
+ 2,
+ $routes['/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)'],
+ 'Font faces collection for the given font family does not have exactly two elements'
+ );
+ }
+
+ public function test_font_faces_no_autosave_routes() {
+ // @core-merge: Enable this test.
+ $this->markTestSkipped( 'This test only works with WP 6.4 and above. Enable it once 6.5 is released.' );
+ $routes = rest_get_server()->get_routes();
+ $this->assertArrayNotHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves',
+ $routes,
+ 'Font faces autosaves route exists.'
+ );
+ $this->assertArrayNotHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves/(?P[\d]+)',
+ $routes,
+ 'Font faces autosaves by id route exists.'
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_context_param
+ */
+ public function test_context_param() {
+ // Collection.
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] );
+ $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] );
+ $this->assertSame( array( 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+
+ // Single.
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] );
+ $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] );
+ $this->assertSame( array( 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertCount( 2, $data );
+ $this->assertArrayHasKey( '_links', $data[0] );
+ $this->check_font_face_data( $data[0], self::$font_face_id2, $data[0]['_links'] );
+ $this->assertArrayHasKey( '_links', $data[1] );
+ $this->check_font_face_data( $data[1], self::$font_face_id1, $data[1]['_links'] );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items_no_permission() {
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items_missing_parent() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->check_font_face_data( $data, self::$font_face_id1, $response->get_links() );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ */
+ public function test_get_item_removes_extra_settings() {
+ $font_face_id = self::create_font_face_post( self::$font_family_id, array( 'extra' => array() ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertArrayNotHasKey( 'extra', $data['font_face_settings'] );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ */
+ public function test_get_item_malformed_post_content_returns_empty_settings() {
+ $font_face_id = wp_insert_post(
+ array(
+ 'post_type' => 'wp_font_face',
+ 'post_parent' => self::$font_family_id,
+ 'post_status' => 'publish',
+ 'post_content' => 'invalid',
+ )
+ );
+
+ $empty_settings = array(
+ 'fontFamily' => '',
+ 'src' => array(),
+ );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( $empty_settings, $data['font_face_settings'] );
+
+ wp_delete_post( $font_face_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_invalid_font_face_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_no_permission() {
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_missing_parent() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_valid_parent_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_invalid_parent_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404 );
+
+ $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"';
+ $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item() {
+ wp_set_current_user( self::$admin_id );
+ $files = $this->setup_font_file_upload( array( 'woff2' ) );
+
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'fontStyle' => 'normal',
+ 'src' => array_keys( $files )[0],
+ )
+ )
+ );
+ $request->set_file_params( $files );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->check_font_face_data( $data, $data['id'], $response->get_links() );
+ $this->check_file_meta( $data['id'], array( $data['font_face_settings']['src'] ) );
+
+ $settings = $data['font_face_settings'];
+ unset( $settings['src'] );
+ $this->assertSame(
+ $settings,
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'fontStyle' => 'normal',
+ )
+ );
+
+ $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_with_multiple_font_files() {
+ wp_set_current_user( self::$admin_id );
+ $files = $this->setup_font_file_upload( array( 'ttf', 'otf', 'woff', 'woff2' ) );
+
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'fontStyle' => 'normal',
+ 'src' => array_keys( $files ),
+ )
+ )
+ );
+ $request->set_file_params( $files );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->check_font_face_data( $data, $data['id'], $response->get_links() );
+ $this->check_file_meta( $data['id'], $data['font_face_settings']['src'] );
+
+ $settings = $data['font_face_settings'];
+ $this->assertCount( 4, $settings['src'] );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_invalid_file_type() {
+ $image_file = DIR_TESTDATA . '/images/canola.jpg';
+ $image_path = wp_tempnam( 'canola.jpg' );
+ copy( $image_file, $image_path );
+
+ $files = array(
+ 'file-0' => array(
+ 'name' => 'canola.jpg',
+ 'full_path' => 'canola.jpg',
+ 'type' => 'font/woff2',
+ 'tmp_name' => $image_path,
+ 'error' => 0,
+ 'size' => filesize( $image_path ),
+ ),
+ );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array_merge(
+ self::$default_settings,
+ array(
+ 'fontWeight' => '200',
+ 'src' => array_keys( $files )[0],
+ )
+ )
+ )
+ );
+ $request->set_file_params( $files );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_font_upload_invalid_file_type', $response, 400 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_with_url_src() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'fontStyle' => 'normal',
+ 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf',
+ )
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->check_font_face_data( $data, $data['id'], $response->get_links() );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_with_all_properties() {
+ wp_set_current_user( self::$admin_id );
+
+ $properties = array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '300 500',
+ 'fontStyle' => 'oblique 30deg 50deg',
+ 'fontDisplay' => 'swap',
+ 'fontStretch' => 'expanded',
+ 'ascentOverride' => '70%',
+ 'descentOverride' => '30%',
+ 'fontVariant' => 'normal',
+ 'fontFeatureSettings' => '"swsh" 2',
+ 'fontVariationSettings' => '"xhgt" 0.7',
+ 'lineGapOverride' => '10%',
+ 'sizeAdjust' => '90%',
+ 'unicodeRange' => 'U+0025-00FF, U+4??',
+ 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg',
+ 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf',
+ );
+
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_face_settings', wp_json_encode( $properties ) );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->assertArrayHasKey( 'font_face_settings', $data );
+ $this->assertSame( $properties, $data['font_face_settings'] );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_missing_parent() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode( array_merge( self::$default_settings, array( 'fontWeight' => '100' ) ) )
+ );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_with_duplicate_properties() {
+ $settings = array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'fontStyle' => 'italic',
+ 'src' => home_url( '/wp-content/fonts/open-sans-italic-light.ttf' ),
+ );
+ $font_face_id = self::create_font_face_post( self::$font_family_id, $settings );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'font_face_settings', wp_json_encode( $settings ) );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_duplicate_font_face', $response, 400 );
+ $expected_message = 'A font face matching those settings already exists.';
+ $message = $response->as_error()->get_error_messages()[0];
+ $this->assertSame( $expected_message, $message );
+
+ wp_delete_post( $font_face_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_request
+ */
+ public function test_create_item_default_theme_json_version() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '200',
+ 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf',
+ )
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->assertArrayHasKey( 'theme_json_version', $data );
+ $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @dataProvider data_create_item_invalid_theme_json_version
+ *
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_invalid_theme_json_version( $theme_json_version ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', $theme_json_version );
+ $request->set_param( 'font_face_settings', '' );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ }
+
+ public function data_create_item_invalid_theme_json_version() {
+ return array(
+ array( 1 ),
+ array( 3 ),
+ );
+ }
+
+ /**
+ * @dataProvider data_create_item_invalid_settings
+ *
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ */
+ public function test_create_item_invalid_settings( $settings ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_face_settings', wp_json_encode( $settings ) );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ }
+
+ public function data_create_item_invalid_settings() {
+ return array(
+ 'Missing fontFamily' => array(
+ 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ),
+ ),
+ 'Empty fontFamily' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ),
+ ),
+ 'Wrong fontFamily type' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ),
+ ),
+ 'Invalid fontDisplay' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'fontDisplay' => 'invalid' ) ),
+ ),
+ 'Missing src' => array(
+ 'settings' => array_diff_key( self::$default_settings, array( 'src' => '' ) ),
+ ),
+ 'Empty src string' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'src' => '' ) ),
+ ),
+ 'Empty src array' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'src' => array() ) ),
+ ),
+ 'Empty src array values' => array(
+ 'settings' => array_merge( self::$default_settings, array( '', '' ) ),
+ ),
+ 'Wrong src type' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'src' => 1234 ) ),
+ ),
+ 'Wrong src array types' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'src' => array( 1234, 5678 ) ) ),
+ ),
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ */
+ public function test_create_item_invalid_settings_json() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_face_settings', 'invalid' );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ $expected_message = 'font_face_settings parameter must be a valid JSON string.';
+ $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings'];
+ $this->assertSame( $expected_message, $message );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ */
+ public function test_create_item_invalid_file_src() {
+ $files = $this->setup_font_file_upload( array( 'woff2' ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array_merge( self::$default_settings, array( 'src' => 'invalid' ) )
+ )
+ );
+ $request->set_file_params( $files );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ $expected_message = 'File ' . array_keys( $files )[0] . ' must be used in font_face_settings[src].';
+ $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings'];
+ $this->assertSame( $expected_message, $message );
+ }
+
+ /**
+ * @dataProvider data_create_item_santize_font_family
+ *
+ * @covers WP_REST_Font_Face_Controller::sanitize_font_face_settings
+ */
+ public function test_create_item_santize_font_family( $font_family_setting, $expected ) {
+ $settings = array_merge( self::$default_settings, array( 'fontFamily' => $font_family_setting ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'font_face_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->assertSame( $expected, $data['font_face_settings']['fontFamily'] );
+ }
+
+ public function data_create_item_santize_font_family() {
+ return array(
+ array( 'Libre Barcode 128 Text', '"Libre Barcode 128 Text"' ),
+ array( 'B612 Mono', '"B612 Mono"' ),
+ array( 'Open Sans, Noto Sans, sans-serif', '"Open Sans", "Noto Sans", sans-serif' ),
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ // public function test_create_item_no_permission() {}
+
+ public function test_update_item() {
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_no_route', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item() {
+ wp_set_current_user( self::$admin_id );
+ $font_face_id = self::create_font_face_post( self::$font_family_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $request->set_param( 'force', true );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertNull( get_post( $font_face_id ) );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_no_trash() {
+ wp_set_current_user( self::$admin_id );
+ $font_face_id = self::create_font_face_post( self::$font_family_id );
+
+ // Attempt trashing.
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
+
+ $request->set_param( 'force', 'false' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
+
+ // Ensure the post still exists.
+ $post = get_post( $font_face_id );
+ $this->assertNotEmpty( $post );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_invalid_font_face_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
+ $request->set_param( 'force', true );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete
+ */
+ public function test_delete_item_missing_parent() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 );
+ $request->set_param( 'force', true );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_delete_item_invalid_parent_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 );
+ $request->set_param( 'force', true );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404 );
+
+ $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"';
+ $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_no_permissions() {
+ $font_face_id = $this->create_font_face_post( self::$font_family_id );
+
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_delete', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ */
+ public function test_prepare_item() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id2 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->check_font_face_data( $data, self::$font_face_id2, $response->get_links() );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item_schema
+ */
+ public function test_get_item_schema() {
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $properties = $data['schema']['properties'];
+ $this->assertCount( 4, $properties );
+ $this->assertArrayHasKey( 'id', $properties );
+ $this->assertArrayHasKey( 'theme_json_version', $properties );
+ $this->assertArrayHasKey( 'parent', $properties );
+ $this->assertArrayHasKey( 'font_face_settings', $properties );
+ }
+
+ protected function check_font_face_data( $data, $post_id, $links ) {
+ $post = get_post( $post_id );
+
+ $this->assertArrayHasKey( 'id', $data );
+ $this->assertSame( $post->ID, $data['id'] );
+
+ $this->assertArrayHasKey( 'parent', $data );
+ $this->assertSame( $post->post_parent, $data['parent'] );
+
+ $this->assertArrayHasKey( 'theme_json_version', $data );
+ $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'] );
+
+ $this->assertArrayHasKey( 'font_face_settings', $data );
+ $this->assertSame( $post->post_content, wp_json_encode( $data['font_face_settings'] ) );
+
+ $this->assertNotEmpty( $links );
+ $this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), $links['self'][0]['href'] );
+ $this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces' ), $links['collection'][0]['href'] );
+ $this->assertSame( rest_url( 'wp/v2/font-families/' . $post->post_parent ), $links['parent'][0]['href'] );
+ }
+
+ protected function check_file_meta( $font_face_id, $srcs ) {
+ $file_meta = get_post_meta( $font_face_id, '_wp_font_face_file' );
+
+ foreach ( $srcs as $src ) {
+ $file_name = basename( $src );
+ $this->assertContains( $file_name, $file_meta, 'The uploaded font file path should be saved in the post meta.' );
+ }
+ }
+
+ protected function setup_font_file_upload( $formats ) {
+ $files = array();
+ foreach ( $formats as $format ) {
+ // @core-merge Use `DIR_TESTDATA` instead of `GUTENBERG_DIR_TESTDATA`.
+ $font_file = GUTENBERG_DIR_TESTDATA . 'fonts/OpenSans-Regular.' . $format;
+ $font_path = wp_tempnam( 'OpenSans-Regular.' . $format );
+ copy( $font_file, $font_path );
+
+ $files[ 'file-' . count( $files ) ] = array(
+ 'name' => 'OpenSans-Regular.' . $format,
+ 'full_path' => 'OpenSans-Regular.' . $format,
+ 'type' => 'font/' . $format,
+ 'tmp_name' => $font_path,
+ 'error' => 0,
+ 'size' => filesize( $font_path ),
+ );
+ }
+
+ return $files;
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php
new file mode 100644
index 00000000000000..6e6a822a9881fb
--- /dev/null
+++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php
@@ -0,0 +1,866 @@
+ 'Open Sans',
+ 'slug' => 'open-sans',
+ 'fontFamily' => '"Open Sans", sans-serif',
+ 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg',
+ );
+
+ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
+ self::$admin_id = $factory->user->create(
+ array(
+ 'role' => 'administrator',
+ )
+ );
+ self::$editor_id = $factory->user->create(
+ array(
+ 'role' => 'editor',
+ )
+ );
+
+ self::$font_family_id1 = self::create_font_family_post(
+ array(
+ 'name' => 'Open Sans',
+ 'slug' => 'open-sans',
+ 'fontFamily' => '"Open Sans", sans-serif',
+ 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg',
+ )
+ );
+ self::$font_family_id2 = self::create_font_family_post(
+ array(
+ 'name' => 'Helvetica',
+ 'slug' => 'helvetica',
+ 'fontFamily' => 'Helvetica, Arial, sans-serif',
+ )
+ );
+ self::$font_face_id1 = WP_REST_Font_Faces_Controller_Test::create_font_face_post(
+ self::$font_family_id1,
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '400',
+ 'fontStyle' => 'normal',
+ 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ),
+ )
+ );
+ self::$font_face_id2 = WP_REST_Font_Faces_Controller_Test::create_font_face_post(
+ self::$font_family_id1,
+ array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontWeight' => '900',
+ 'fontStyle' => 'normal',
+ 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ),
+ )
+ );
+ }
+
+ public static function wpTearDownAfterClass() {
+ self::delete_user( self::$admin_id );
+ self::delete_user( self::$editor_id );
+ }
+
+ public static function create_font_family_post( $settings = array() ) {
+ $settings = array_merge( self::$default_settings, $settings );
+ return self::factory()->post->create(
+ wp_slash(
+ array(
+ 'post_type' => 'wp_font_family',
+ 'post_status' => 'publish',
+ 'post_title' => $settings['name'],
+ 'post_name' => $settings['slug'],
+ 'post_content' => wp_json_encode(
+ array(
+ 'fontFamily' => $settings['fontFamily'],
+ 'preview' => $settings['preview'],
+ )
+ ),
+ )
+ )
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::register_routes
+ */
+ public function test_register_routes() {
+ $routes = rest_get_server()->get_routes();
+ $this->assertArrayHasKey(
+ '/wp/v2/font-families',
+ $routes,
+ 'Font faces collection for the given font family does not exist'
+ );
+ $this->assertCount(
+ 2,
+ $routes['/wp/v2/font-families'],
+ 'Font faces collection for the given font family does not have exactly two elements'
+ );
+ $this->assertArrayHasKey(
+ '/wp/v2/font-families/(?P[\d]+)',
+ $routes,
+ 'Single font face route for the given font family does not exist'
+ );
+ $this->assertCount(
+ 3,
+ $routes['/wp/v2/font-families/(?P[\d]+)'],
+ 'Font faces collection for the given font family does not have exactly two elements'
+ );
+ }
+
+ public function test_font_families_no_autosave_routes() {
+ // @core-merge: Enable this test.
+ $this->markTestSkipped( 'This test only works with WP 6.4 and above. Enable it once 6.5 is released.' );
+ $routes = rest_get_server()->get_routes();
+ $this->assertArrayNotHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/autosaves',
+ $routes,
+ 'Font families autosaves route exists.'
+ );
+ $this->assertArrayNotHasKey(
+ '/wp/v2/font-families/(?P[\d]+)/autosaves/(?P[\d]+)',
+ $routes,
+ 'Font families autosaves by id route exists.'
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Families_Controller::get_context_param
+ */
+ public function test_context_param() {
+ // Collection.
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] );
+ $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] );
+ $this->assertSame( array( 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+
+ // Single.
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] );
+ $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] );
+ $this->assertSame( array( 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
+ }
+
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertCount( 2, $data );
+ $this->assertArrayHasKey( '_links', $data[0] );
+ $this->check_font_family_data( $data[0], self::$font_family_id2, $data[0]['_links'] );
+ $this->assertArrayHasKey( '_links', $data[1] );
+ $this->check_font_family_data( $data[1], self::$font_family_id1, $data[1]['_links'] );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items_by_slug() {
+ $font_family = get_post( self::$font_family_id2 );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' );
+ $request->set_param( 'slug', $font_family->post_name );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertCount( 1, $data );
+ $this->assertSame( $font_family->ID, $data[0]['id'] );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_items
+ */
+ public function test_get_items_no_permission() {
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->check_font_family_data( $data, self::$font_family_id1, $response->get_links() );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ */
+ public function test_get_item_embedded_font_faces() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $request->set_param( '_embed', true );
+ $response = rest_get_server()->dispatch( $request );
+ $data = rest_get_server()->response_to_data( $response, true );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertArrayHasKey( '_embedded', $data );
+ $this->assertArrayHasKey( 'font_faces', $data['_embedded'] );
+ $this->assertCount( 2, $data['_embedded']['font_faces'] );
+
+ foreach ( $data['_embedded']['font_faces'] as $font_face ) {
+ $this->assertArrayHasKey( 'id', $font_face );
+
+ $font_face_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 . '/font-faces/' . $font_face['id'] );
+ $font_face_response = rest_get_server()->dispatch( $font_face_request );
+ $font_face_data = rest_get_server()->response_to_data( $font_face_response, true );
+
+ $this->assertSame( $font_face_data, $font_face );
+ }
+ }
+
+ /**
+ * @covers WP_REST_Font_Families_Controller::get_item
+ */
+ public function test_get_item_removes_extra_settings() {
+ $font_family_id = self::create_font_family_post( array( 'fontFace' => array() ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertArrayNotHasKey( 'fontFace', $data['font_family_settings'] );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Families_Controller::prepare_item_for_response
+ */
+ public function test_get_item_malformed_post_content_returns_empty_settings() {
+ $font_family_id = wp_insert_post(
+ array(
+ 'post_type' => 'wp_font_family',
+ 'post_status' => 'publish',
+ 'post_content' => 'invalid',
+ )
+ );
+
+ $empty_settings = array(
+ 'name' => '',
+ // Slug will default to the post id.
+ 'slug' => (string) $font_family_id,
+ 'fontFamily' => '',
+ 'preview' => '',
+ );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( $empty_settings, $data['font_family_settings'] );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_invalid_font_family_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item
+ */
+ public function test_get_item_no_permission() {
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item() {
+ $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->check_font_family_data( $data, $data['id'], $response->get_links() );
+
+ $reponse_settings = $data['font_family_settings'];
+ $this->assertSame( $settings, $reponse_settings );
+ $this->assertEmpty( $data['font_faces'] );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_request
+ */
+ public function test_create_item_default_theme_json_version() {
+ $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $this->assertArrayHasKey( 'theme_json_version', $data );
+ $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ /**
+ * @dataProvider data_create_item_invalid_theme_json_version
+ *
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_invalid_theme_json_version( $theme_json_version ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', $theme_json_version );
+ $request->set_param( 'font_family_settings', wp_json_encode( self::$default_settings ) );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ }
+
+ public function data_create_item_invalid_theme_json_version() {
+ return array(
+ array( 1 ),
+ array( 3 ),
+ );
+ }
+
+ /**
+ * @dataProvider data_create_item_with_default_preview
+ *
+ * @covers WP_REST_Font_Faces_Controller::sanitize_font_family_settings
+ */
+ public function test_create_item_with_default_preview( $settings ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 201, $response->get_status() );
+ $response_settings = $data['font_family_settings'];
+ $this->assertArrayHasKey( 'preview', $response_settings );
+ $this->assertSame( '', $response_settings['preview'] );
+
+ wp_delete_post( $data['id'], true );
+ }
+
+ public function data_create_item_with_default_preview() {
+ $default_settings = array(
+ 'name' => 'Open Sans',
+ 'slug' => 'open-sans-2',
+ 'fontFamily' => '"Open Sans", sans-serif',
+ );
+ return array(
+ 'No preview param' => array(
+ 'settings' => $default_settings,
+ ),
+ 'Empty preview' => array(
+ 'settings' => array_merge( $default_settings, array( 'preview' => '' ) ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider data_create_item_invalid_settings
+ *
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ */
+ public function test_create_item_invalid_settings( $settings ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ }
+
+ public function data_create_item_invalid_settings() {
+ return array(
+ 'Missing name' => array(
+ 'settings' => array_diff_key( self::$default_settings, array( 'name' => '' ) ),
+ ),
+ 'Empty name' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'name' => '' ) ),
+ ),
+ 'Wrong name type' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'name' => 1234 ) ),
+ ),
+ 'Missing slug' => array(
+ 'settings' => array_diff_key( self::$default_settings, array( 'slug' => '' ) ),
+ ),
+ 'Empty slug' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'slug' => '' ) ),
+ ),
+ 'Wrong slug type' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'slug' => 1234 ) ),
+ ),
+ 'Missing fontFamily' => array(
+ 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ),
+ ),
+ 'Empty fontFamily' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ),
+ ),
+ 'Wrong fontFamily type' => array(
+ 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ),
+ ),
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Family_Controller::validate_font_family_settings
+ */
+ public function test_create_item_invalid_settings_json() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_family_settings', 'invalid' );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ $expected_message = 'font_family_settings parameter must be a valid JSON string.';
+ $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings'];
+ $this->assertSame( $expected_message, $message );
+ }
+
+ /**
+ * @covers WP_REST_Font_Family_Controller::create_item
+ */
+ public function test_create_item_with_duplicate_slug() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param( 'font_family_settings', wp_json_encode( array_merge( self::$default_settings, array( 'slug' => 'helvetica' ) ) ) );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_duplicate_font_family', $response, 400 );
+ $expected_message = 'A font family with slug "helvetica" already exists.';
+ $message = $response->as_error()->get_error_messages()[0];
+ $this->assertSame( $expected_message, $message );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::create_item
+ */
+ public function test_create_item_no_permission() {
+ $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_create', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param(
+ 'font_family_settings',
+ wp_json_encode(
+ array(
+ 'name' => 'Open Sans',
+ 'slug' => 'open-sans',
+ 'fontFamily' => '"Open Sans", sans-serif',
+ 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg',
+ )
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Families_Controller::update_item
+ */
+ public function test_update_item() {
+ wp_set_current_user( self::$admin_id );
+
+ $settings = array(
+ 'name' => 'Open Sans',
+ 'fontFamily' => '"Open Sans, "Noto Sans", sans-serif',
+ 'preview' => 'https://s.w.org/images/fonts/16.9/previews/open-sans/open-sans-400-normal.svg',
+ );
+
+ $font_family_id = self::create_font_family_post( array( 'slug' => 'open-sans-2' ) );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id );
+ $request->set_param(
+ 'font_family_settings',
+ wp_json_encode( $settings )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->check_font_family_data( $data, $font_family_id, $response->get_links() );
+
+ $expected_settings = array(
+ 'name' => $settings['name'],
+ 'slug' => 'open-sans-2',
+ 'fontFamily' => $settings['fontFamily'],
+ 'preview' => $settings['preview'],
+ );
+ $this->assertSame( $expected_settings, $data['font_family_settings'] );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ /**
+ * @dataProvider data_update_item_individual_settings
+ * @covers WP_REST_Font_Families_Controller::update_item
+ */
+ public function test_update_item_individual_settings( $settings ) {
+ wp_set_current_user( self::$admin_id );
+
+ $font_family_id = self::create_font_family_post();
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $key = key( $settings );
+ $value = current( $settings );
+ $this->assertArrayHasKey( $key, $data['font_family_settings'] );
+ $this->assertSame( $value, $data['font_family_settings'][ $key ] );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ public function data_update_item_individual_settings() {
+ return array(
+ array( array( 'name' => 'Opened Sans' ) ),
+ array( array( 'fontFamily' => '"Opened Sans", sans-serif' ) ),
+ array( array( 'preview' => 'https://s.w.org/images/fonts/16.7/previews/opened-sans/opened-sans-400-normal.svg' ) ),
+ // Empty preview is allowed.
+ array( array( 'preview' => '' ) ),
+ );
+ }
+
+ /**
+ * @dataProvider data_update_item_santize_font_family
+ *
+ * @covers WP_REST_Font_Families_Controller::sanitize_font_face_settings
+ */
+ public function test_update_item_santize_font_family( $font_family_setting, $expected ) {
+ wp_set_current_user( self::$admin_id );
+
+ $font_family_id = self::create_font_family_post();
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id );
+ $request->set_param( 'font_family_settings', wp_json_encode( array( 'fontFamily' => $font_family_setting ) ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( $expected, $data['font_family_settings']['fontFamily'] );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ public function data_update_item_santize_font_family() {
+ return array(
+ array( 'Libre Barcode 128 Text', '"Libre Barcode 128 Text"' ),
+ array( 'B612 Mono', '"B612 Mono"' ),
+ array( 'Open Sans, Noto Sans, sans-serif', '"Open Sans", "Noto Sans", sans-serif' ),
+ );
+ }
+
+ /**
+ * @dataProvider data_update_item_invalid_settings
+ *
+ * @covers WP_REST_Font_Faces_Controller::update_item
+ */
+ public function test_update_item_empty_settings( $settings ) {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $request->set_param(
+ 'font_family_settings',
+ wp_json_encode( $settings )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ }
+
+ public function data_update_item_invalid_settings() {
+ return array(
+ 'Empty name' => array(
+ array( 'name' => '' ),
+ ),
+ 'Wrong name type' => array(
+ array( 'name' => 1234 ),
+ ),
+ 'Empty fontFamily' => array(
+ array( 'fontFamily' => '' ),
+ ),
+ 'Wrong fontFamily type' => array(
+ array( 'fontFamily' => 1234 ),
+ ),
+ );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::update_item
+ */
+ public function test_update_item_update_slug_not_allowed() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $request->set_param(
+ 'font_family_settings',
+ wp_json_encode( array( 'slug' => 'new-slug' ) )
+ );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+ $expected_message = 'font_family_settings[slug] cannot be updated.';
+ $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings'];
+ $this->assertSame( $expected_message, $message );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::update_item
+ */
+ public function test_update_item_invalid_font_family_id() {
+ $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::update_item
+ */
+ public function test_update_item_no_permission() {
+ $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) );
+
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_edit', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
+ }
+
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item() {
+ wp_set_current_user( self::$admin_id );
+ $font_family_id = self::create_font_family_post();
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id );
+ $request['force'] = true;
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertNull( get_post( $font_family_id ) );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_no_trash() {
+ wp_set_current_user( self::$admin_id );
+ $font_family_id = self::create_font_family_post();
+
+ // Attempt trashing.
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
+
+ $request->set_param( 'force', 'false' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
+
+ // Ensure the post still exists.
+ $post = get_post( $font_family_id );
+ $this->assertNotEmpty( $post );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_invalid_font_family_id() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::delete_item
+ */
+ public function test_delete_item_no_permissions() {
+ $font_family_id = self::create_font_family_post();
+
+ wp_set_current_user( 0 );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_delete', $response, 401 );
+
+ wp_set_current_user( self::$editor_id );
+ $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
+
+ wp_delete_post( $font_family_id, true );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ */
+ public function test_prepare_item() {
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id2 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->check_font_family_data( $data, self::$font_family_id2, $response->get_links() );
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item_schema
+ */
+ public function test_get_item_schema() {
+ $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status() );
+ $properties = $data['schema']['properties'];
+ $this->assertCount( 4, $properties );
+ $this->assertArrayHasKey( 'id', $properties );
+ $this->assertArrayHasKey( 'theme_json_version', $properties );
+ $this->assertArrayHasKey( 'font_faces', $properties );
+ $this->assertArrayHasKey( 'font_family_settings', $properties );
+ }
+
+ protected function check_font_family_data( $data, $post_id, $links ) {
+ $post = get_post( $post_id );
+
+ $this->assertArrayHasKey( 'id', $data );
+ $this->assertSame( $post->ID, $data['id'] );
+
+ $this->assertArrayHasKey( 'theme_json_version', $data );
+ $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'] );
+
+ $font_face_ids = get_children(
+ array(
+ 'fields' => 'ids',
+ 'post_parent' => $post_id,
+ 'post_type' => 'wp_font_face',
+ 'order' => 'ASC',
+ 'orderby' => 'ID',
+ )
+ );
+ $this->assertArrayHasKey( 'font_faces', $data );
+
+ foreach ( $font_face_ids as $font_face_id ) {
+ $this->assertContains( $font_face_id, $data['font_faces'] );
+ }
+
+ $this->assertArrayHasKey( 'font_family_settings', $data );
+ $settings = $data['font_family_settings'];
+ $expected_settings = array(
+ 'name' => $post->post_title,
+ 'slug' => $post->post_name,
+ 'fontFamily' => $settings['fontFamily'],
+ 'preview' => $settings['preview'],
+ );
+ $this->assertSame( $expected_settings, $settings );
+
+ $this->assertNotEmpty( $links );
+ $this->assertSame( rest_url( 'wp/v2/font-families/' . $post->ID ), $links['self'][0]['href'] );
+ $this->assertSame( rest_url( 'wp/v2/font-families' ), $links['collection'][0]['href'] );
+
+ if ( ! $font_face_ids ) {
+ return;
+ }
+
+ // Check font_face links, if present.
+ $this->assertArrayHasKey( 'font_faces', $links );
+ foreach ( $links['font_faces'] as $index => $link ) {
+ $this->assertSame( rest_url( 'wp/v2/font-families/' . $post->ID . '/font-faces/' . $font_face_ids[ $index ] ), $link['href'] );
+
+ $embeddable = isset( $link['attributes']['embeddable'] )
+ ? $link['attributes']['embeddable']
+ : $link['embeddable'];
+ $this->assertTrue( $embeddable );
+ }
+ }
+}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php
deleted file mode 100644
index e2d190cd76af1f..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php
+++ /dev/null
@@ -1,43 +0,0 @@
-factory->user->create(
- array(
- 'role' => 'administrator',
- )
- );
- wp_set_current_user( $admin_id );
- }
-
- /**
- * Tear down each test method.
- */
- public function tear_down() {
- parent::tear_down();
-
- // Clean up the /fonts directory.
- foreach ( $this->files_in_dir( static::$fonts_dir ) as $file ) {
- @unlink( $file );
- }
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php
deleted file mode 100644
index 98c1cb6e13fe5c..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php
+++ /dev/null
@@ -1,334 +0,0 @@
-set_param( 'font_family_settings', $font_family_json );
- $install_request->set_file_params( $files );
- $response = rest_get_server()->dispatch( $install_request );
- $data = $response->get_data();
- $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
- $this->assertCount( count( $expected_response['successes'] ), $data['successes'], 'Not all the font families were installed correctly.' );
-
- // Checks that the font families were installed correctly.
- for ( $family_index = 0; $family_index < count( $data['successes'] ); $family_index++ ) {
- $installed_font = $data['successes'][ $family_index ];
- $expected_font = $expected_response['successes'][ $family_index ];
-
- if ( isset( $installed_font['fontFace'] ) || isset( $expected_font['fontFace'] ) ) {
- for ( $face_index = 0; $face_index < count( $installed_font['fontFace'] ); $face_index++ ) {
- // Checks that the font asset were created correctly.
- if ( isset( $installed_font['fontFace'][ $face_index ]['src'] ) ) {
- $this->assertStringEndsWith( $expected_font['fontFace'][ $face_index ]['src'], $installed_font['fontFace'][ $face_index ]['src'], 'The src of the fonts were not updated as expected.' );
- }
- // Removes the src from the response to compare the rest of the data.
- unset( $installed_font['fontFace'][ $face_index ]['src'] );
- unset( $expected_font['fontFace'][ $face_index ]['src'] );
- unset( $installed_font['fontFace'][ $face_index ]['uploadedFile'] );
- }
- }
-
- // Compares if the rest of the data is the same.
- $this->assertEquals( $expected_font, $installed_font, 'The endpoint answer is not as expected.' );
- }
- }
-
- /**
- * Data provider for test_install_fonts
- */
- public function data_install_fonts() {
-
- $temp_file_path1 = wp_tempnam( 'Piazzola1-' );
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $temp_file_path1 );
-
- $temp_file_path2 = wp_tempnam( 'Monteserrat-' );
- copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $temp_file_path2 );
-
- return array(
-
- 'google_fonts_to_download' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- 'files' => array(),
- 'expected_response' => array(
- 'successes' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf',
- ),
- ),
- ),
- ),
- 'errors' => array(),
- ),
- ),
-
- 'google_fonts_to_use_as_is' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- 'files' => array(),
- 'expected_response' => array(
- 'successes' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- ),
- 'errors' => array(),
- ),
- ),
-
- 'fonts_without_font_faces' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Arial',
- 'slug' => 'arial',
- 'name' => 'Arial',
- ),
- 'files' => array(),
- 'expected_response' => array(
- 'successes' => array(
- array(
- 'fontFamily' => 'Arial',
- 'slug' => 'arial',
- 'name' => 'Arial',
- ),
- ),
- 'errors' => array(),
- ),
- ),
-
- 'fonts_with_local_fonts_assets' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files' => array(
- 'files0' => array(
- 'name' => 'piazzola1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => $temp_file_path1,
- 'error' => 0,
- 'size' => 123,
- ),
- 'files1' => array(
- 'name' => 'montserrat1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => $temp_file_path2,
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- 'expected_response' => array(
- 'successes' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf',
- ),
- ),
- ),
- ),
- 'errors' => array(),
- ),
- ),
- );
- }
-
- /**
- * Tests failure when fonfaces has improper inputs
- *
- * @dataProvider data_install_with_improper_inputs
- *
- * @param array $font_families Font families to install in theme.json format.
- * @param array $files Font files to install.
- */
- public function test_install_with_improper_inputs( $font_families, $files = array() ) {
- $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
- $font_families_json = json_encode( $font_families );
- $install_request->set_param( 'font_families', $font_families_json );
- $install_request->set_file_params( $files );
-
- $response = rest_get_server()->dispatch( $install_request );
- $this->assertSame( 400, $response->get_status() );
- }
-
- /**
- * Data provider for test_install_with_improper_inputs
- */
- public function data_install_with_improper_inputs() {
- $temp_file_path1 = wp_tempnam( 'Piazzola1-' );
- file_put_contents( $temp_file_path1, 'Mocking file content' );
-
- return array(
- 'not a font families array' => array(
- 'font_family_settings' => 'This is not an array',
- ),
-
- 'empty array' => array(
- 'font_family_settings' => array(),
- ),
-
- 'without slug' => array(
- 'font_family_settings' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- ),
- ),
- ),
-
- 'with improper font face property' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFace' => 'This is not an array',
- ),
- ),
-
- 'with empty font face property' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFace' => array(),
- ),
- ),
-
- 'fontface referencing uploaded file without uploaded files' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- 'files' => array(),
- ),
-
- 'fontface referencing uploaded file without uploaded files' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'name' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'uploadedFile' => 'files666',
- ),
- ),
- ),
- 'files' => array(
- 'files0' => array(
- 'name' => 'piazzola1.ttf',
- 'type' => 'font/ttf',
- 'tmp_name' => $temp_file_path1,
- 'error' => 0,
- 'size' => 123,
- ),
- ),
- ),
-
- 'fontface with incompatible properties (downloadFromUrl and uploadedFile together)' => array(
- 'font_family_settings' => array(
- 'fontFamily' => 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'uploadedFile' => 'files0',
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/uninstallFonts.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/uninstallFonts.php
deleted file mode 100644
index 241f26284fe5d2..00000000000000
--- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/uninstallFonts.php
+++ /dev/null
@@ -1,96 +0,0 @@
- 'Piazzolla',
- 'slug' => 'piazzolla',
- 'name' => 'Piazzolla',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Piazzolla',
- 'fontStyle' => 'normal',
- 'fontWeight' => '400',
- 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf',
- ),
- ),
- ),
- array(
- 'fontFamily' => 'Montserrat',
- 'slug' => 'montserrat',
- 'name' => 'Montserrat',
- 'fontFace' => array(
- array(
- 'fontFamily' => 'Montserrat',
- 'fontStyle' => 'normal',
- 'fontWeight' => '100',
- 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf',
- 'downloadFromUrl' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf',
- ),
- ),
- ),
- );
-
- $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
- $font_families_json = json_encode( $mock_families );
- $install_request->set_param( 'font_families', $font_families_json );
- rest_get_server()->dispatch( $install_request );
- }
-
- public function test_uninstall() {
- $font_families_to_uninstall = array(
- array(
- 'slug' => 'piazzolla',
- ),
- array(
- 'slug' => 'montserrat',
- ),
- );
-
- $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families' );
- $uninstall_request->set_param( 'font_families', $font_families_to_uninstall );
- $response = rest_get_server()->dispatch( $uninstall_request );
- $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
- }
-
-
- public function test_uninstall_non_existing_fonts() {
- $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families' );
-
- $non_existing_font_data = array(
- array(
- 'slug' => 'non-existing-font',
- 'name' => 'Non existing font',
- ),
- array(
- 'slug' => 'another-not-installed-font',
- 'name' => 'Another not installed font',
- ),
- );
-
- $uninstall_request->set_param( 'font_families', $non_existing_font_data );
- $response = rest_get_server()->dispatch( $uninstall_request );
- $data = $response->get_data();
- $this->assertCount( 2, $data['errors'], 'The response should have 2 errors, one for each font family uninstall failure.' );
- }
-}
diff --git a/schemas/json/block.json b/schemas/json/block.json
index fd69ea1badb339..5658361d7aab64 100644
--- a/schemas/json/block.json
+++ b/schemas/json/block.json
@@ -316,6 +316,11 @@
"type": "object",
"description": "This value signals that a block supports some of the CSS style properties related to dimensions. When it does, the block editor will show UI controls for the user to set their values if the theme declares support.\n\nWhen the block declares support for a specific dimensions property, its attributes definition is extended to include the style attribute.",
"properties": {
+ "aspectRatio": {
+ "type": "boolean",
+ "description": "Allow blocks to define an aspect ratio value.",
+ "default": false
+ },
"minHeight": {
"type": "boolean",
"description": "Allow blocks to define a minimum height value.",
@@ -543,6 +548,11 @@
}
}
},
+ "shadow": {
+ "type": "boolean",
+ "description": "Allow blocks to define a box shadow.",
+ "default": false
+ },
"typography": {
"type": "object",
"description": "This value signals that a block supports some of the CSS style properties related to typography. When it does, the block editor will show UI controls for the user to set their values if the theme declares support.\n\nWhen the block declares support for a specific typography property, its attributes definition is extended to include the style attribute.",
@@ -614,6 +624,7 @@
"type": "object",
"properties": {
"root": { "type": "string" },
+ "aspectRatio": { "type": "string" },
"minHeight": { "type": "string" }
}
}
diff --git a/schemas/json/theme.json b/schemas/json/theme.json
index 6ae8d15df63d2d..7f12e4f7097765 100644
--- a/schemas/json/theme.json
+++ b/schemas/json/theme.json
@@ -20,7 +20,7 @@
"type": "object",
"properties": {
"appearanceTools": {
- "description": "Setting that enables the following UI tools:\n\n- background: backgroundImage\n- border: color, radius, style, width\n- color: link\n- dimensions: minHeight\n- position: sticky\n- spacing: blockGap, margin, padding\n- typography: lineHeight",
+ "description": "Setting that enables the following UI tools:\n\n- background: backgroundImage\n- border: color, radius, style, width\n- color: link\n- dimensions: aspectRatio, minHeight\n- position: sticky\n- spacing: blockGap, margin, padding\n- typography: lineHeight",
"type": "boolean",
"default": false
}
@@ -271,6 +271,11 @@
"description": "Settings related to dimensions.",
"type": "object",
"properties": {
+ "aspectRatio": {
+ "description": "Allow users to set an aspect ratio.",
+ "type": "boolean",
+ "default": false
+ },
"minHeight": {
"description": "Allow users to set custom minimum height.",
"type": "boolean",
@@ -1419,6 +1424,17 @@
"description": "Dimensions styles",
"type": "object",
"properties": {
+ "aspectRatio": {
+ "description": "Sets the `aspect-ratio` CSS property.",
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/definitions/refComplete"
+ }
+ ]
+ },
"minHeight": {
"description": "Sets the `min-height` CSS property.",
"oneOf": [
diff --git a/test/e2e/specs/editor/blocks/links.spec.js b/test/e2e/specs/editor/blocks/links.spec.js
index 9620b45fed9da8..d1de2df1da7ff7 100644
--- a/test/e2e/specs/editor/blocks/links.spec.js
+++ b/test/e2e/specs/editor/blocks/links.spec.js
@@ -445,6 +445,9 @@ test.describe( 'Links', () => {
await pageUtils.pressKeys( 'Enter' );
const linkPopover = LinkUtils.getLinkPopover();
+ await expect( linkPopover ).toBeVisible();
+ // Close the link control to return the caret to the canvas
+ await pageUtils.pressKeys( 'Escape' );
// Deselect the link text by moving the caret to the end of the line
// and the link popover should not be displayed.
@@ -612,6 +615,13 @@ test.describe( 'Links', () => {
await page.keyboard.type( 'w.org' );
await page.keyboard.press( 'Enter' );
+ // Close the link control to return the caret to the canvas
+ const linkPopover = LinkUtils.getLinkPopover();
+ await pageUtils.pressKeys( 'Escape' );
+ // Deselect the link text by moving the caret to the end of the line
+ // and the link popover should not be displayed.
+ await pageUtils.pressKeys( 'End' );
+ await expect( linkPopover ).toBeHidden();
await expect.poll( editor.getBlocks ).toMatchObject( [
{
@@ -634,17 +644,12 @@ test.describe( 'Links', () => {
await page.getByPlaceholder( 'Search or type url' ).fill( '' );
await page.keyboard.type( 'wordpress.org' );
- const linkPopover = LinkUtils.getLinkPopover();
-
// Update the link.
await linkPopover.getByRole( 'button', { name: 'Save' } ).click();
- // Navigate back to the popover.
- await page.keyboard.press( 'ArrowLeft' );
- await page.keyboard.press( 'ArrowLeft' );
-
- // Navigate back to inputs to verify appears as changed.
- await pageUtils.pressKeys( 'primary+k' );
+ // Navigate back to the link editing state inputs to verify appears as changed.
+ await page.keyboard.press( 'Tab' );
+ await page.keyboard.press( 'Enter' );
expect(
await page
@@ -683,20 +688,21 @@ test.describe( 'Links', () => {
await pageUtils.pressKeys( 'primary+k' );
await page.keyboard.type( 'w.org' );
await page.keyboard.press( 'Enter' );
+ await page.keyboard.press( 'Escape' );
// Move to edge of text "Gutenberg".
await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' ); // If you just use Alt here it won't work on windows.
await pageUtils.pressKeys( 'ArrowLeft' );
- await pageUtils.pressKeys( 'ArrowLeft' );
// Select "Gutenberg".
- await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' );
+ await pageUtils.pressKeys( 'shiftAlt+ArrowRight' );
// Create a link.
await pageUtils.pressKeys( 'primary+k' );
await page.keyboard.type( 'https://wordpress.org/plugins/gutenberg/' );
await page.keyboard.press( 'Enter' );
-
+ await page.keyboard.press( 'Escape' );
+ await pageUtils.pressKeys( 'End' );
// Move back into the link.
await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' );
await pageUtils.pressKeys( 'primary+k' );
@@ -1054,6 +1060,9 @@ test.describe( 'Links', () => {
// Update the link.
await pageUtils.pressKeys( 'Enter' );
+ await pageUtils.pressKeys( 'Escape' );
+ await pageUtils.pressKeys( 'ArrowRight' );
+
// Reactivate the link.
await pageUtils.pressKeys( 'ArrowLeft' );
await pageUtils.pressKeys( 'ArrowLeft' );
@@ -1117,11 +1126,10 @@ test.describe( 'Links', () => {
// Update the link.
await pageUtils.pressKeys( 'Enter' );
+ await pageUtils.pressKeys( 'Escape' );
// Move cursor next to the **end** of `linkTextOne`
- await pageUtils.pressKeys( 'ArrowLeft', {
- times: linkedTextTwo.length,
- } );
+ await pageUtils.pressKeys( 'ArrowLeft' );
// Select `linkTextOne`
await pageUtils.pressKeys( 'shiftAlt+ArrowLeft' );
@@ -1134,6 +1142,8 @@ test.describe( 'Links', () => {
// Update the link.
await pageUtils.pressKeys( 'Enter' );
+ await pageUtils.pressKeys( 'Escape' );
+ await pageUtils.pressKeys( 'ArrowRight' );
// Move cursor within `linkTextOne`
await pageUtils.pressKeys( 'ArrowLeft', {
@@ -1146,8 +1156,8 @@ test.describe( 'Links', () => {
await expect( linkPopover ).toBeVisible();
// Expand selection so that it overlaps with `linkTextTwo`
- await pageUtils.pressKeys( 'ArrowRight', {
- times: 3,
+ await pageUtils.pressKeys( 'Shift+ArrowRight', {
+ times: 6,
} );
// Link UI should be inactive.
@@ -1254,6 +1264,8 @@ class LinkUtils {
// Click on the Submit button.
await this.pageUtils.pressKeys( 'Enter' );
+ await this.pageUtils.pressKeys( 'Escape' );
+ await this.pageUtils.pressKeys( 'End' );
// Reselect the link.
await this.pageUtils.pressKeys( 'shiftAlt+ArrowLeft' );
diff --git a/test/e2e/specs/editor/blocks/paragraph.spec.js b/test/e2e/specs/editor/blocks/paragraph.spec.js
index 142b01be282d48..dd3cd9196efb29 100644
--- a/test/e2e/specs/editor/blocks/paragraph.spec.js
+++ b/test/e2e/specs/editor/blocks/paragraph.spec.js
@@ -252,8 +252,9 @@ test.describe( 'Paragraph', () => {
{
// Dragging on the bottom half of the heading block.
+ // Make sure to target the bottom dropzone by dragging > 30px inside the block.
await draggingUtils.dragOver(
- headingBox.x,
+ headingBox.x + 32,
headingBox.y + headingBox.height - 1
);
await expect( draggingUtils.dropZone ).toBeHidden();
@@ -268,6 +269,26 @@ test.describe( 'Paragraph', () => {
)
.toBeGreaterThan( headingBox.y + headingBox.height );
}
+
+ {
+ // Dragging on the right edge of the heading block.
+ // Targets the right hand dropzone.
+ await draggingUtils.dragOver(
+ headingBox.x + headingBox.width - 1,
+ headingBox.y + headingBox.height - 1
+ );
+ await expect( draggingUtils.dropZone ).toBeHidden();
+ await expect(
+ draggingUtils.insertionIndicator
+ ).toBeVisible();
+ await expect
+ .poll( () =>
+ draggingUtils.insertionIndicator
+ .boundingBox()
+ .then( ( { x, width } ) => x + width )
+ )
+ .toBe( headingBox.x + headingBox.width );
+ }
} );
test( 'Only the second block is an empty paragraph block', async ( {
@@ -299,7 +320,7 @@ test.describe( 'Paragraph', () => {
{
// Dragging on the top half of the heading block.
await draggingUtils.dragOver(
- headingBox.x,
+ headingBox.x + 32,
headingBox.y + 1
);
await expect( draggingUtils.dropZone ).toBeHidden();
@@ -318,7 +339,7 @@ test.describe( 'Paragraph', () => {
{
// Dragging on the bottom half of the heading block.
await draggingUtils.dragOver(
- headingBox.x,
+ headingBox.x + 32,
headingBox.y + headingBox.height - 1
);
await expect( draggingUtils.dropZone ).toBeVisible();
diff --git a/test/e2e/specs/editor/various/draggable-blocks.spec.js b/test/e2e/specs/editor/various/draggable-blocks.spec.js
index 29a81f57540e06..e08030191dd60b 100644
--- a/test/e2e/specs/editor/various/draggable-blocks.spec.js
+++ b/test/e2e/specs/editor/various/draggable-blocks.spec.js
@@ -134,9 +134,10 @@ test.describe( 'Draggable block', () => {
const secondParagraphBound = await secondParagraph.boundingBox();
// Call the move function twice to make sure the `dragOver` event is sent.
// @see https://github.com/microsoft/playwright/issues/17153
+ // Make sure mouse is > 30px within the block for bottom drop indicator to appear.
for ( let i = 0; i < 2; i += 1 ) {
await page.mouse.move(
- secondParagraphBound.x,
+ secondParagraphBound.x + 32,
secondParagraphBound.y + secondParagraphBound.height * 0.75
);
}
diff --git a/test/e2e/specs/editor/various/inserting-blocks.spec.js b/test/e2e/specs/editor/various/inserting-blocks.spec.js
index 4d26a198fc3455..a000f02eaca8c2 100644
--- a/test/e2e/specs/editor/various/inserting-blocks.spec.js
+++ b/test/e2e/specs/editor/various/inserting-blocks.spec.js
@@ -451,7 +451,8 @@ class InsertingBlocksUtils {
for ( let i = 0; i < 2; i += 1 ) {
await this.page.mouse.move(
// Hover on the right side of the block to avoid collapsing with the preview.
- boundingBox.x + boundingBox.width - 1,
+ // But not too far to avoid triggering the grouping block inserter.
+ boundingBox.x + boundingBox.width - 32,
// Hover on the bottom of the paragraph block.
boundingBox.y + boundingBox.height - 1
);
diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js
index 674801cf94abab..00f21b4e51c5ea 100644
--- a/test/e2e/specs/editor/various/list-view.spec.js
+++ b/test/e2e/specs/editor/various/list-view.spec.js
@@ -53,11 +53,40 @@ test.describe( 'List View', () => {
name: 'Paragraph',
exact: true,
} );
+ const imageBlockItem = listView.getByRole( 'gridcell', {
+ name: 'Image',
+ exact: true,
+ } );
const headingBlockItem = listView.getByRole( 'gridcell', {
name: 'Heading',
exact: true,
} );
- await paragraphBlockItem.dragTo( headingBlockItem, { x: 0, y: 0 } );
+
+ await paragraphBlockItem.hover();
+ await page.mouse.down();
+
+ // To work around a drag and drop bug in Safari, the list view applies
+ // `pointer-events: none` to the list view while dragging, so that
+ // `onDragLeave` is not fired when dragging within the list view.
+ // Without the `force: true` option, the `hover` action will fail
+ // as playwright will complain that pointer-events are intercepted.
+ // https://bugs.webkit.org/show_bug.cgi?id=66547
+ // See: https://github.com/WordPress/gutenberg/pull/56625
+
+ // Hover over each block to mimic moving up the list view.
+ // Also, hover twice to ensure a dragover event is dispatched.
+ // See: https://playwright.dev/docs/input#dragging-manually
+ await imageBlockItem.hover( { force: true } );
+ await imageBlockItem.hover( { force: true } );
+ await headingBlockItem.hover( { force: true } );
+ await headingBlockItem.hover( { force: true } );
+
+ // Disable reason: Need to wait until the throttle timeout of 250ms has passed.
+ /* eslint-disable playwright/no-wait-for-timeout */
+ await editor.page.waitForTimeout( 300 );
+ /* eslint-enable playwright/no-wait-for-timeout */
+
+ await page.mouse.up();
// Ensure the block was dropped correctly.
await expect
diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js
index 818a05881f53e6..ee60091e057c02 100644
--- a/test/e2e/specs/editor/various/pattern-overrides.spec.js
+++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js
@@ -7,9 +7,6 @@ test.describe( 'Pattern Overrides', () => {
test.beforeAll( async ( { requestUtils } ) => {
await Promise.all( [
requestUtils.activateTheme( 'emptytheme' ),
- requestUtils.setGutenbergExperiments( [
- 'gutenberg-pattern-partial-syncing',
- ] ),
requestUtils.deleteAllBlocks(),
] );
} );
@@ -20,7 +17,6 @@ test.describe( 'Pattern Overrides', () => {
test.afterAll( async ( { requestUtils } ) => {
await Promise.all( [
- requestUtils.setGutenbergExperiments( [] ),
requestUtils.activateTheme( 'twentytwentyone' ),
] );
} );
@@ -176,7 +172,7 @@ test.describe( 'Pattern Overrides', () => {
ref: patternId,
overrides: {
[ editableParagraphId ]: {
- content: 'I would word it this way',
+ content: [ 1, 'I would word it this way' ],
},
},
},
@@ -187,7 +183,7 @@ test.describe( 'Pattern Overrides', () => {
ref: patternId,
overrides: {
[ editableParagraphId ]: {
- content: 'This one is different',
+ content: [ 1, 'This one is different' ],
},
},
},
@@ -216,4 +212,113 @@ test.describe( 'Pattern Overrides', () => {
] );
} );
} );
+
+ test( 'retains override values when converting a pattern block to regular blocks', async ( {
+ page,
+ admin,
+ requestUtils,
+ editor,
+ } ) => {
+ const paragraphId = 'paragraph-id';
+ const { id } = await requestUtils.createBlock( {
+ title: 'Pattern',
+ content: `
+Editable
+`,
+ status: 'publish',
+ } );
+
+ await admin.createNewPost();
+
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: { ref: id },
+ } );
+
+ // Make an edit to the pattern.
+ await editor.canvas
+ .getByRole( 'document', { name: 'Block: Paragraph' } )
+ .focus();
+ await page.keyboard.type( 'edited ' );
+
+ // Convert back to regular blocks.
+ await editor.selectBlocks(
+ editor.canvas.getByRole( 'document', { name: 'Block: Pattern' } )
+ );
+ await editor.showBlockToolbar();
+ await editor.clickBlockOptionsMenuItem( 'Detach' );
+
+ // Check that the overrides remain.
+ await expect.poll( editor.getBlocks ).toMatchObject( [
+ {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'edited Editable',
+ metadata: undefined,
+ },
+ },
+ ] );
+ } );
+
+ test( 'Supports `undefined` attribute values in patterns', async ( {
+ page,
+ admin,
+ editor,
+ requestUtils,
+ } ) => {
+ const buttonId = 'button-id';
+ const { id } = await requestUtils.createBlock( {
+ title: 'Pattern with overrides',
+ content: `
+
+`,
+ status: 'publish',
+ } );
+
+ await admin.createNewPost();
+
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: { ref: id },
+ } );
+
+ await editor.canvas
+ .getByRole( 'document', { name: 'Block: Button' } )
+ .getByRole( 'textbox', { name: 'Button text' } )
+ .focus();
+
+ await expect(
+ page.getByRole( 'link', { name: 'wp.org' } )
+ ).toContainText( 'opens in a new tab' );
+
+ const openInNewTabCheckbox = page.getByRole( 'checkbox', {
+ name: 'Open in new tab',
+ } );
+ await expect( openInNewTabCheckbox ).toBeChecked();
+
+ await openInNewTabCheckbox.setChecked( false );
+
+ await expect.poll( editor.getBlocks ).toMatchObject( [
+ {
+ name: 'core/block',
+ attributes: {
+ ref: id,
+ overrides: {
+ [ buttonId ]: {
+ linkTarget: [ 0 ],
+ },
+ },
+ },
+ },
+ ] );
+
+ const postId = await editor.publishPost();
+ await page.goto( `/?p=${ postId }` );
+
+ const link = page.getByRole( 'link', { name: 'wp.org' } );
+ await expect( link ).toHaveAttribute( 'href', 'http://wp.org' );
+ await expect( link ).toHaveAttribute( 'target', '' );
+ } );
} );
diff --git a/test/e2e/specs/editor/various/patterns.spec.js b/test/e2e/specs/editor/various/patterns.spec.js
index 4b47b2dd70a358..745f5a5aa417bf 100644
--- a/test/e2e/specs/editor/various/patterns.spec.js
+++ b/test/e2e/specs/editor/various/patterns.spec.js
@@ -141,7 +141,16 @@ test.describe( 'Synced pattern', () => {
{
name: 'core/block',
attributes: { ref: expect.any( Number ) },
- innerBlocks: [],
+ innerBlocks: [
+ {
+ attributes: {
+ content: 'A useful paragraph to reuse',
+ dropCap: false,
+ },
+ innerBlocks: [],
+ name: 'core/paragraph',
+ },
+ ],
},
] );
const after = await editor.getBlocks();
@@ -167,4 +176,149 @@ test.describe( 'Synced pattern', () => {
await expect.poll( editor.getBlocks ).toEqual( [ ...after, ...after ] );
} );
+
+ // Check for regressions of https://github.com/WordPress/gutenberg/issues/33072.
+ test( 'can be saved when modified inside of a published post', async ( {
+ page,
+ requestUtils,
+ editor,
+ } ) => {
+ const { id } = await requestUtils.createBlock( {
+ title: 'Alternative greeting block',
+ content:
+ '\nGuten Tag!
\n',
+ status: 'publish',
+ } );
+
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: { ref: id },
+ } );
+
+ await editor.publishPost();
+
+ await editor.selectBlocks(
+ editor.canvas.getByRole( 'document', { name: 'Block: Pattern' } )
+ );
+ await editor.showBlockToolbar();
+ await page
+ .getByRole( 'toolbar', { name: 'Block tools' } )
+ .getByRole( 'link', { name: 'Edit original' } )
+ .click();
+
+ const editorTopBar = page.getByRole( 'region', {
+ name: 'Editor top bar',
+ } );
+
+ // Navigate to the pattern focus mode.
+ await expect(
+ editorTopBar.getByRole( 'heading', {
+ name: 'Alternative greeting block',
+ level: 1,
+ } )
+ ).toBeVisible();
+
+ await editor.selectBlocks(
+ editor.canvas.getByRole( 'document', { name: 'Block: Paragraph' } )
+ );
+
+ // Change the block's content.
+ await page.keyboard.type( 'Einen ' );
+
+ // Save the reusable block and update the post.
+ await editorTopBar.getByRole( 'button', { name: 'Update' } ).click();
+ await page
+ .getByRole( 'button', { name: 'Dismiss this notice' } )
+ .filter( { hasText: 'Pattern updated.' } )
+ .click();
+
+ // Go back to the post.
+ await editorTopBar.getByRole( 'button', { name: 'Back' } ).click();
+
+ await expect.poll( editor.getBlocks ).toEqual( [
+ {
+ name: 'core/block',
+ attributes: { ref: id },
+ innerBlocks: [
+ {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Einen Guten Tag!',
+ dropCap: false,
+ },
+ innerBlocks: [],
+ },
+ ],
+ },
+ ] );
+ } );
+
+ // Check for regressions of https://github.com/WordPress/gutenberg/issues/26421.
+ test( 'allows conversion back to blocks when the reusable block has unsaved edits', async ( {
+ page,
+ requestUtils,
+ editor,
+ } ) => {
+ const { id } = await requestUtils.createBlock( {
+ title: 'Synced pattern',
+ content:
+ '\nBefore Edit
\n',
+ status: 'publish',
+ } );
+
+ await editor.insertBlock( {
+ name: 'core/block',
+ attributes: { ref: id },
+ } );
+
+ await editor.selectBlocks(
+ editor.canvas.getByRole( 'document', { name: 'Block: Pattern' } )
+ );
+ await page
+ .getByRole( 'toolbar', { name: 'Block tools' } )
+ .getByRole( 'link', { name: 'Edit original' } )
+ .click();
+
+ const editorTopBar = page.getByRole( 'region', {
+ name: 'Editor top bar',
+ } );
+
+ // Navigate to the pattern focus mode.
+ await expect(
+ editorTopBar.getByRole( 'heading', {
+ name: 'Synced pattern',
+ level: 1,
+ } )
+ ).toBeVisible();
+
+ // Make an edit to the source pattern.
+ await editor.canvas
+ .getByRole( 'document', { name: 'Block: Paragraph' } )
+ .fill( 'After Edit' );
+
+ // Go back to the post.
+ await editorTopBar.getByRole( 'button', { name: 'Back' } ).click();
+
+ const expectedParagraphBlock = {
+ name: 'core/paragraph',
+ attributes: { content: 'After Edit' },
+ };
+
+ await expect.poll( editor.getBlocks ).toMatchObject( [
+ {
+ name: 'core/block',
+ attributes: { ref: id },
+ innerBlocks: [ expectedParagraphBlock ],
+ },
+ ] );
+
+ await editor.selectBlocks(
+ editor.canvas.getByRole( 'document', { name: 'Block: Pattern' } )
+ );
+ await editor.clickBlockOptionsMenuItem( 'Detach' );
+
+ await expect
+ .poll( editor.getBlocks )
+ .toMatchObject( [ expectedParagraphBlock ] );
+ } );
} );
diff --git a/test/e2e/specs/interactivity/directive-each.spec.ts b/test/e2e/specs/interactivity/directive-each.spec.ts
new file mode 100644
index 00000000000000..4f024b1f828a95
--- /dev/null
+++ b/test/e2e/specs/interactivity/directive-each.spec.ts
@@ -0,0 +1,486 @@
+/**
+ * Internal dependencies
+ */
+import { test, expect } from './fixtures';
+
+test.describe( 'data-wp-each', () => {
+ test.beforeAll( async ( { interactivityUtils: utils } ) => {
+ await utils.activatePlugins();
+ await utils.addPostWithBlock( 'test/directive-each' );
+ } );
+
+ test.beforeEach( async ( { interactivityUtils: utils, page } ) => {
+ await page.goto( utils.getLink( 'test/directive-each' ) );
+ } );
+
+ test.afterAll( async ( { interactivityUtils: utils } ) => {
+ await utils.deactivatePlugins();
+ await utils.deleteAllPosts();
+ } );
+
+ test( 'should use `item` as the defaul item name in the context', async ( {
+ page,
+ } ) => {
+ const elements = page.getByTestId( 'letters' ).getByTestId( 'item' );
+ await expect( elements ).toHaveText( [ 'A', 'B', 'C' ] );
+ } );
+
+ test( 'should use the specified item name in the context', async ( {
+ page,
+ } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+ await expect( elements ).toHaveText( [
+ 'avocado',
+ 'banana',
+ 'cherimoya',
+ ] );
+ } );
+
+ test.describe( 'without `wp-each-key`', () => {
+ test.beforeEach( async ( { page } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+
+ // These tags are included to check that the elements are not unmounted
+ // and mounted again. If an element remounts, its tag should be missing.
+ await elements.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+ } );
+
+ test( 'should preserve elements on deletion', async ( { page } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+
+ // An item is removed when clicked.
+ await elements.first().click();
+
+ await expect( elements ).toHaveText( [ 'banana', 'cherimoya' ] );
+ await expect( elements.getByText( 'avocado' ) ).toBeHidden();
+
+ // Get the tags. They should not have disappeared.
+ const [ banana, cherimoya ] = await elements.all();
+ await expect( banana ).toHaveAttribute( 'data-tag', '1' );
+ await expect( cherimoya ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should preserve elements on reordering', async ( { page } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'fruits' ).getByTestId( 'rotate' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'cherimoya',
+ 'avocado',
+ 'banana',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed.
+ const [ cherimoya, avocado, banana ] = await elements.all();
+ await expect( cherimoya ).toHaveAttribute( 'data-tag', '2' );
+ await expect( avocado ).toHaveAttribute( 'data-tag', '0' );
+ await expect( banana ).toHaveAttribute( 'data-tag', '1' );
+ } );
+
+ test( 'should preserve elements on addition', async ( { page } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'fruits' ).getByTestId( 'add' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'ananas',
+ 'avocado',
+ 'banana',
+ 'cherimoya',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed,
+ // except for the newly created element.
+ const [ ananas, avocado, banana, cherimoya ] = await elements.all();
+ await expect( ananas ).not.toHaveAttribute( 'data-tag' );
+ await expect( avocado ).toHaveAttribute( 'data-tag', '0' );
+ await expect( banana ).toHaveAttribute( 'data-tag', '1' );
+ await expect( cherimoya ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should preserve elements on replacement', async ( { page } ) => {
+ const elements = page.getByTestId( 'fruits' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'fruits' ).getByTestId( 'replace' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'ananas',
+ 'banana',
+ 'cherimoya',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed,
+ // except for the newly created element.
+ const [ ananas, banana, cherimoya ] = await elements.all();
+ await expect( ananas ).not.toHaveAttribute( 'data-tag' );
+ await expect( banana ).toHaveAttribute( 'data-tag', '1' );
+ await expect( cherimoya ).toHaveAttribute( 'data-tag', '2' );
+ } );
+ } );
+
+ test.describe( 'with `wp-each-key`', () => {
+ test.beforeEach( async ( { page } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ // These tags are included to check that the elements are not unmounted
+ // and mounted again. If an element remounts, its tag should be missing.
+ await elements.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+ } );
+
+ test( 'should preserve elements on deletion', async ( { page } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ await expect( elements ).toHaveText( [
+ 'A Game of Thrones',
+ 'A Clash of Kings',
+ 'A Storm of Swords',
+ ] );
+
+ // An item is removed when clicked.
+ await elements.first().click();
+
+ await expect( elements ).toHaveText( [
+ 'A Clash of Kings',
+ 'A Storm of Swords',
+ ] );
+
+ // Get the tags. They should not have disappeared.
+ const [ acok, asos ] = await elements.all();
+ await expect( acok ).toHaveAttribute( 'data-tag', '1' );
+ await expect( asos ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should preserve elements on reordering', async ( { page } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'books' ).getByTestId( 'rotate' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'A Storm of Swords',
+ 'A Game of Thrones',
+ 'A Clash of Kings',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed.
+ const [ asos, agot, acok ] = await elements.all();
+ await expect( asos ).toHaveAttribute( 'data-tag', '2' );
+ await expect( agot ).toHaveAttribute( 'data-tag', '0' );
+ await expect( acok ).toHaveAttribute( 'data-tag', '1' );
+ } );
+
+ test( 'should preserve elements on addition', async ( { page } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'books' ).getByTestId( 'add' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'A Feast for Crows',
+ 'A Game of Thrones',
+ 'A Clash of Kings',
+ 'A Storm of Swords',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed,
+ // except for the newly created element.
+ const [ affc, agot, acok, asos ] = await elements.all();
+ await expect( affc ).not.toHaveAttribute( 'data-tag' );
+ await expect( agot ).toHaveAttribute( 'data-tag', '0' );
+ await expect( acok ).toHaveAttribute( 'data-tag', '1' );
+ await expect( asos ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should preserve elements on replacement', async ( { page } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'books' ).getByTestId( 'replace' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'A Feast for Crows',
+ 'A Clash of Kings',
+ 'A Storm of Swords',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed,
+ // except for the newly created element.
+ const [ affc, acok, asos ] = await elements.all();
+ await expect( affc ).not.toHaveAttribute( 'data-tag' );
+ await expect( acok ).toHaveAttribute( 'data-tag', '1' );
+ await expect( asos ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should preserve elements on modification', async ( {
+ page,
+ } ) => {
+ const elements = page.getByTestId( 'books' ).getByTestId( 'item' );
+
+ await page.getByTestId( 'books' ).getByTestId( 'modify' ).click();
+
+ await expect( elements ).toHaveText( [
+ 'A GAME OF THRONES',
+ 'A Clash of Kings',
+ 'A Storm of Swords',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed.
+ const [ agot, acok, asos ] = await elements.all();
+ await expect( agot ).toHaveAttribute( 'data-tag', '0' );
+ await expect( acok ).toHaveAttribute( 'data-tag', '1' );
+ await expect( asos ).toHaveAttribute( 'data-tag', '2' );
+ } );
+ } );
+
+ test( 'should respect elements after', async ( { page } ) => {
+ const elements = page.getByTestId( 'numbers' ).getByTestId( 'item' );
+ await expect( elements ).toHaveText( [ '1', '2', '3', '4' ] );
+ await page.getByTestId( 'numbers' ).getByTestId( 'shift' ).click();
+ await expect( elements ).toHaveText( [ '2', '3', '4' ] );
+ await page
+ .getByTestId( 'numbers' )
+ .getByTestId( 'unshift' )
+ .click( { clickCount: 2 } );
+ await expect( elements ).toHaveText( [ '0', '1', '2', '3', '4' ] );
+ } );
+
+ test( 'should support initial empty lists', async ( { page } ) => {
+ const elements = page.getByTestId( 'empty' ).getByTestId( 'item' );
+ await expect( elements ).toHaveText( [ 'item X' ] );
+ await page
+ .getByTestId( 'empty' )
+ .getByTestId( 'add' )
+ .click( { clickCount: 2 } );
+
+ await expect( elements ).toHaveText( [ 'item 0', 'item 1', 'item X' ] );
+ } );
+
+ test( 'should support multiple siblings inside the template', async ( {
+ page,
+ } ) => {
+ const elements = page.getByTestId( 'siblings' ).getByTestId( 'item' );
+ await expect( elements ).toHaveText( [
+ 'two',
+ '2',
+ 'three',
+ '3',
+ 'four',
+ '4',
+ ] );
+ await page.getByTestId( 'siblings' ).getByTestId( 'unshift' ).click();
+ await expect( elements ).toHaveText( [
+ 'one',
+ '1',
+ 'two',
+ '2',
+ 'three',
+ '3',
+ 'four',
+ '4',
+ ] );
+ } );
+
+ test( 'should work on navigation', async ( { page } ) => {
+ const elements = page
+ .getByTestId( 'navigation-updated list' )
+ .getByTestId( 'item' );
+
+ // These tags are included to check that the elements are not unmounted
+ // and mounted again. If an element remounts, its tag should be missing.
+ await elements.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+
+ await expect( elements ).toHaveText( [ 'beta', 'gamma', 'delta' ] );
+
+ await page
+ .getByTestId( 'navigation-updated list' )
+ .getByTestId( 'navigate' )
+ .click();
+
+ await expect( elements ).toHaveText( [
+ 'alpha',
+ 'beta',
+ 'gamma',
+ 'delta',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed,
+ // except for the newly created element.
+ const [ alpha, beta, gamma, delta ] = await elements.all();
+ await expect( alpha ).not.toHaveAttribute( 'data-tag' );
+ await expect( beta ).toHaveAttribute( 'data-tag', '0' );
+ await expect( gamma ).toHaveAttribute( 'data-tag', '1' );
+ await expect( delta ).toHaveAttribute( 'data-tag', '2' );
+ } );
+
+ test( 'should work with nested lists', async ( { page } ) => {
+ const mainElement = page.getByTestId( 'nested' );
+
+ // These tags are included to check that the elements are not unmounted
+ // and mounted again. If an element remounts, its tag should be missing.
+ const listItems = mainElement.getByRole( 'listitem' );
+ await listItems.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+
+ const animals = mainElement.getByTestId( 'animal' );
+
+ {
+ // Ensure it hydrates correctly.
+ const [ dog, cat ] = await animals.all();
+ await expect( dog.getByTestId( 'name' ) ).toHaveText( 'Dog' );
+ await expect( dog.getByRole( 'listitem' ) ).toHaveText( [
+ 'chihuahua',
+ 'rottweiler',
+ ] );
+ await expect( cat.getByTestId( 'name' ) ).toHaveText( 'Cat' );
+ await expect( cat.getByRole( 'listitem' ) ).toHaveText( [
+ 'sphynx',
+ 'siamese',
+ ] );
+ }
+
+ await mainElement.getByTestId( 'add animal' ).click();
+
+ {
+ // Ensure it works when the top list is modified.
+ const [ rat, dog, cat ] = await animals.all();
+ await expect( rat.getByTestId( 'name' ) ).toHaveText( 'Rat' );
+ await expect( rat.getByRole( 'listitem' ) ).toHaveText( [
+ 'dumbo',
+ 'rex',
+ ] );
+ await expect( dog.getByTestId( 'name' ) ).toHaveText( 'Dog' );
+ await expect( dog.getByRole( 'listitem' ) ).toHaveText( [
+ 'chihuahua',
+ 'rottweiler',
+ ] );
+ await expect( cat.getByTestId( 'name' ) ).toHaveText( 'Cat' );
+ await expect( cat.getByRole( 'listitem' ) ).toHaveText( [
+ 'sphynx',
+ 'siamese',
+ ] );
+ await expect( rat ).not.toHaveAttribute( 'data-tag' );
+ const [ d1, d2 ] = await dog.getByRole( 'listitem' ).all();
+ await expect( dog ).toHaveAttribute( 'data-tag', '0' );
+ await expect( d1 ).toHaveAttribute( 'data-tag', '1' );
+ await expect( d2 ).toHaveAttribute( 'data-tag', '2' );
+ const [ c1, c2 ] = await cat.getByRole( 'listitem' ).all();
+ await expect( cat ).toHaveAttribute( 'data-tag', '3' );
+ await expect( c1 ).toHaveAttribute( 'data-tag', '4' );
+ await expect( c2 ).toHaveAttribute( 'data-tag', '5' );
+ }
+
+ // Reset tags so the added elements have one.
+ await listItems.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+
+ await mainElement.getByTestId( 'add breeds' ).click();
+
+ {
+ // Ensure it works when the top list is modified.
+ const [ rat, dog, cat ] = await animals.all();
+ await expect( rat.getByTestId( 'name' ) ).toHaveText( 'Rat' );
+ await expect( rat.getByRole( 'listitem' ) ).toHaveText( [
+ 'satin',
+ 'dumbo',
+ 'rex',
+ ] );
+ await expect( dog.getByTestId( 'name' ) ).toHaveText( 'Dog' );
+ await expect( dog.getByRole( 'listitem' ) ).toHaveText( [
+ 'german shepherd',
+ 'chihuahua',
+ 'rottweiler',
+ ] );
+ await expect( cat.getByTestId( 'name' ) ).toHaveText( 'Cat' );
+ await expect( cat.getByRole( 'listitem' ) ).toHaveText( [
+ 'maine coon',
+ 'sphynx',
+ 'siamese',
+ ] );
+ const [ r1, r2, r3 ] = await rat.getByRole( 'listitem' ).all();
+ await expect( rat ).toHaveAttribute( 'data-tag', '0' );
+ await expect( r1 ).not.toHaveAttribute( 'data-tag' );
+ await expect( r2 ).toHaveAttribute( 'data-tag', '1' );
+ await expect( r3 ).toHaveAttribute( 'data-tag', '2' );
+ const [ d1, d2, d3 ] = await dog.getByRole( 'listitem' ).all();
+ await expect( dog ).toHaveAttribute( 'data-tag', '3' );
+ await expect( d1 ).not.toHaveAttribute( 'data-tag' );
+ await expect( d2 ).toHaveAttribute( 'data-tag', '4' );
+ await expect( d3 ).toHaveAttribute( 'data-tag', '5' );
+ const [ c1, c2, c3 ] = await cat.getByRole( 'listitem' ).all();
+ await expect( cat ).toHaveAttribute( 'data-tag', '6' );
+ await expect( c1 ).not.toHaveAttribute( 'data-tag' );
+ await expect( c2 ).toHaveAttribute( 'data-tag', '7' );
+ await expect( c3 ).toHaveAttribute( 'data-tag', '8' );
+ }
+ } );
+
+ test( 'should do nothing when used on non-template elements', async ( {
+ page,
+ } ) => {
+ const elements = page
+ .getByTestId( 'invalid tag' )
+ .getByTestId( 'item' );
+
+ await expect( elements ).toHaveCount( 1 );
+ await expect( elements ).toBeEmpty();
+ } );
+
+ test( 'should work with derived state as keys', async ( { page } ) => {
+ const elements = page
+ .getByTestId( 'derived state' )
+ .getByTestId( 'item' );
+
+ // These tags are included to check that the elements are not unmounted
+ // and mounted again. If an element remounts, its tag should be missing.
+ await elements.evaluateAll( ( refs ) =>
+ refs.forEach( ( ref, index ) => {
+ if ( ref instanceof HTMLElement ) {
+ ref.dataset.tag = `${ index }`;
+ }
+ } )
+ );
+
+ await page
+ .getByTestId( 'derived state' )
+ .getByTestId( 'rotate' )
+ .click();
+
+ await expect( elements ).toHaveText( [
+ 'cherimoya',
+ 'avocado',
+ 'banana',
+ ] );
+
+ // Get the tags. They should not have disappeared or changed.
+ const [ cherimoya, avocado, banana ] = await elements.all();
+ await expect( cherimoya ).toHaveAttribute( 'data-tag', '2' );
+ await expect( avocado ).toHaveAttribute( 'data-tag', '0' );
+ await expect( banana ).toHaveAttribute( 'data-tag', '1' );
+ } );
+} );
diff --git a/test/e2e/specs/interactivity/directive-on-document.spec.ts b/test/e2e/specs/interactivity/directive-on-document.spec.ts
index 824e217f087146..02a6ac99d45c42 100644
--- a/test/e2e/specs/interactivity/directive-on-document.spec.ts
+++ b/test/e2e/specs/interactivity/directive-on-document.spec.ts
@@ -30,17 +30,27 @@ test.describe( 'data-wp-on-document', () => {
page,
} ) => {
const counter = page.getByTestId( 'counter' );
+ const isEventAttached = page.getByTestId( 'isEventAttached' );
const visibilityButton = page.getByTestId( 'visibility' );
+
await expect( counter ).toHaveText( '0' );
+ await expect( isEventAttached ).toHaveText( 'yes' );
await page.keyboard.press( 'ArrowDown' );
await expect( counter ).toHaveText( '1' );
+
// Remove the element.
await visibilityButton.click();
// This keyboard press should not increase the counter.
await page.keyboard.press( 'ArrowDown' );
+
// Add the element back.
await visibilityButton.click();
await expect( counter ).toHaveText( '1' );
+
+ // Wait until the effects run again.
+ await expect( isEventAttached ).toHaveText( 'yes' );
+
+ // Check that the event listener is attached again.
await page.keyboard.press( 'ArrowDown' );
await expect( counter ).toHaveText( '2' );
} );
diff --git a/test/e2e/specs/interactivity/directive-on-window.spec.ts b/test/e2e/specs/interactivity/directive-on-window.spec.ts
index e1d59e09cd35f4..dd3a02e0bb4009 100644
--- a/test/e2e/specs/interactivity/directive-on-window.spec.ts
+++ b/test/e2e/specs/interactivity/directive-on-window.spec.ts
@@ -29,16 +29,27 @@ test.describe( 'data-wp-on-window', () => {
page,
} ) => {
const counter = page.getByTestId( 'counter' );
+ const isEventAttached = page.getByTestId( 'isEventAttached' );
const visibilityButton = page.getByTestId( 'visibility' );
+
+ await expect( counter ).toHaveText( '0' );
+ await expect( isEventAttached ).toHaveText( 'yes' );
await page.setViewportSize( { width: 600, height: 600 } );
await expect( counter ).toHaveText( '1' );
+
// Remove the element.
await visibilityButton.click();
+
// This resize should not increase the counter.
await page.setViewportSize( { width: 300, height: 600 } );
+
// Add the element back.
await visibilityButton.click();
await expect( counter ).toHaveText( '1' );
+
+ // Wait until the effects run again.
+ await expect( isEventAttached ).toHaveText( 'yes' );
+
await page.setViewportSize( { width: 200, height: 600 } );
await expect( counter ).toHaveText( '2' );
} );
diff --git a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts
index 44507ad34813e8..94bb383a142cce 100644
--- a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts
+++ b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts
@@ -25,11 +25,16 @@ export default class InteractivityUtils {
);
}
- // Add an extra param to disable directives SSR. This is required at
- // this moment, as SSR for directives is not stabilized yet and we need
- // to ensure hydration works, even when the SSR'ed HTML is not correct.
+ /*
+ * Add an extra param to disable directives SSR. This is required at
+ * this moment, as SSR for directives is not stabilized yet and we need
+ * to ensure hydration works, even when the SSR'ed HTML is not correct.
+ */
const url = new URL( link );
- url.searchParams.append( 'disable_directives_ssr', 'true' );
+ url.searchParams.append(
+ 'disable_server_directive_processing',
+ 'true'
+ );
return url.href;
}
diff --git a/test/e2e/specs/interactivity/with-scope.spec.ts b/test/e2e/specs/interactivity/with-scope.spec.ts
new file mode 100644
index 00000000000000..1cb73cc915aca7
--- /dev/null
+++ b/test/e2e/specs/interactivity/with-scope.spec.ts
@@ -0,0 +1,27 @@
+/**
+ * Internal dependencies
+ */
+import { test, expect } from './fixtures';
+
+test.describe( 'withScope', () => {
+ test.beforeAll( async ( { interactivityUtils: utils } ) => {
+ await utils.activatePlugins();
+ await utils.addPostWithBlock( 'test/with-scope' );
+ } );
+ test.beforeEach( async ( { interactivityUtils: utils, page } ) => {
+ await page.goto( utils.getLink( 'test/with-scope' ) );
+ } );
+ test.afterAll( async ( { interactivityUtils: utils } ) => {
+ await utils.deactivatePlugins();
+ await utils.deleteAllPosts();
+ } );
+
+ test( 'directives using withScope should work with async and sync functions', async ( {
+ page,
+ } ) => {
+ const asyncCounter = page.getByTestId( 'asyncCounter' );
+ await expect( asyncCounter ).toHaveText( '1' );
+ const syncCounter = page.getByTestId( 'syncCounter' );
+ await expect( syncCounter ).toHaveText( '1' );
+ } );
+} );
diff --git a/test/e2e/specs/site-editor/block-removal.spec.js b/test/e2e/specs/site-editor/block-removal.spec.js
index 27d3762364b446..bf63e089c8a0bf 100644
--- a/test/e2e/specs/site-editor/block-removal.spec.js
+++ b/test/e2e/specs/site-editor/block-removal.spec.js
@@ -35,7 +35,7 @@ test.describe( 'Site editor block removal prompt', () => {
// Expect the block removal prompt to have appeared
await expect(
page.getByText(
- 'Post or page content will not be displayed if you delete these blocks.'
+ 'Deleting these blocks will stop your post or page content from displaying on this template. It is not recommended.'
)
).toBeVisible();
} );
@@ -59,7 +59,7 @@ test.describe( 'Site editor block removal prompt', () => {
// Expect the block removal prompt to have appeared
await expect(
page.getByText(
- 'Post or page content will not be displayed if you delete this block.'
+ 'Deleting this block will stop your post or page content from displaying on this template. It is not recommended.'
)
).toBeVisible();
} );
diff --git a/test/e2e/specs/site-editor/new-templates-list.spec.js b/test/e2e/specs/site-editor/new-templates-list.spec.js
index 272a3ffff80bf1..13484abcb13add 100644
--- a/test/e2e/specs/site-editor/new-templates-list.spec.js
+++ b/test/e2e/specs/site-editor/new-templates-list.spec.js
@@ -18,11 +18,7 @@ test.describe( 'Templates', () => {
] );
} );
test( 'Sorting', async ( { admin, page } ) => {
- await admin.visitSiteEditor( { path: '/wp_template' } );
- // Switch to table layout.
- await page.getByLabel( 'View options' ).click();
- await page.getByRole( 'menuitem', { name: 'Layout' } ).click();
- await page.getByRole( 'menuitemradio', { name: 'Table' } ).click();
+ await admin.visitSiteEditor( { path: '/wp_template/all' } );
// Descending by title.
await page
.getByRole( 'button', { name: 'Template', exact: true } )
@@ -52,13 +48,7 @@ test.describe( 'Templates', () => {
title: 'Date Archives',
content: 'hi',
} );
- await admin.visitSiteEditor( { path: '/wp_template' } );
-
- // Switch to table layout.
- await page.getByLabel( 'View options' ).click();
- await page.getByRole( 'menuitem', { name: 'Layout' } ).click();
- await page.getByRole( 'menuitemradio', { name: 'Table' } ).click();
-
+ await admin.visitSiteEditor( { path: '/wp_template/all' } );
// Global search.
await page.getByRole( 'searchbox', { name: 'Filter list' } ).click();
await page.keyboard.type( 'tag' );
@@ -94,13 +84,7 @@ test.describe( 'Templates', () => {
await expect( titles ).toHaveCount( 2 );
} );
test( 'Field visibility', async ( { admin, page } ) => {
- await admin.visitSiteEditor( { path: '/wp_template' } );
-
- // Switch to table layout.
- await page.getByLabel( 'View options' ).click();
- await page.getByRole( 'menuitem', { name: 'Layout' } ).click();
- await page.getByRole( 'menuitemradio', { name: 'Table' } ).click();
-
+ await admin.visitSiteEditor( { path: '/wp_template/all' } );
await page.getByRole( 'button', { name: 'Description' } ).click();
await page.getByRole( 'menuitem', { name: 'Hide' } ).click();
await expect(
diff --git a/test/e2e/specs/site-editor/patterns.spec.js b/test/e2e/specs/site-editor/patterns.spec.js
index e4fffe14fcc9a1..6b2561d1f5bd95 100644
--- a/test/e2e/specs/site-editor/patterns.spec.js
+++ b/test/e2e/specs/site-editor/patterns.spec.js
@@ -13,7 +13,9 @@ const test = base.extend( {
},
} );
-test.describe( 'Patterns', () => {
+// Skip these tests for now as we plan to adapt them to
+// the new patterns UI.
+test.describe.skip( 'Patterns', () => {
test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( 'emptytheme' );
await requestUtils.deleteAllBlocks();
diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js
index ff62da26bcfacf..9cb8ba53461e09 100644
--- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js
+++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js
@@ -14,7 +14,7 @@ test.use( {
},
} );
-test.describe( 'Global styles revisions', () => {
+test.describe( 'Style Revisions', () => {
let stylesPostId;
test.beforeAll( async ( { requestUtils } ) => {
await Promise.all( [
diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js
index d599980b951a93..080ee2e01242bd 100644
--- a/tools/webpack/blocks.js
+++ b/tools/webpack/blocks.js
@@ -35,6 +35,8 @@ const prefixFunctions = [
'wp_get_global_settings',
];
+const classesToSuffix = [ 'WP_Navigation_Block_Renderer' ];
+
/**
* Escapes the RegExp special characters.
*
@@ -139,9 +141,10 @@ module.exports = [
},
transform: ( content ) => {
const prefix = 'gutenberg_';
+ const classSuffix = 'Gutenberg';
content = content.toString();
- // Within content, search and prefix any function calls from
+ // Within content, search and prefix any function calls from the
// `prefixFunctions` list. This is needed because some functions
// are called inside block files, but have been declared elsewhere.
// So with the rename we can call Gutenberg override functions, but the
@@ -158,6 +161,19 @@ module.exports = [
) }`
);
+ // Within content, search and prefix any classes calls from the
+ // `classesToSuffix` list. This is needed because some classes
+ // are called inside block files, but also exist in core.
+ // With the rename we can use the Gutenberg class in the plugin,
+ // without having to worry about duplicate class names with core.
+ content = content.replace(
+ new RegExp(
+ classesToSuffix.join( '|' ),
+ 'g'
+ ),
+ ( match ) => `${ match }_${ classSuffix }`
+ );
+
// Within content, search for any function definitions. For
// each, replace every other reference to it in the file.
return (
diff --git a/tools/webpack/interactivity.js b/tools/webpack/interactivity.js
index 03bb1f576cb783..5dd9192c855661 100644
--- a/tools/webpack/interactivity.js
+++ b/tools/webpack/interactivity.js
@@ -3,6 +3,10 @@
*/
const { join } = require( 'path' );
const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
+/**
+ * WordPress dependencies
+ */
+const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );
/**
* Internal dependencies
@@ -14,6 +18,7 @@ module.exports = {
name: 'interactivity',
entry: {
index: `./packages/interactivity/src/index.js`,
+ router: `./packages/interactivity-router/src/index.js`,
navigation: './packages/block-library/src/navigation/view.js',
query: './packages/block-library/src/query/view.js',
image: './packages/block-library/src/image/view.js',
@@ -31,10 +36,8 @@ module.exports = {
},
path: join( __dirname, '..', '..' ),
environment: { module: true },
- },
- externalsType: 'module',
- externals: {
- '@wordpress/interactivity': '@wordpress/interactivity',
+ module: true,
+ chunkFormat: 'module',
},
resolve: {
extensions: [ '.js', '.ts', '.tsx' ],
@@ -79,6 +82,7 @@ module.exports = {
},
],
} ),
+ new DependencyExtractionWebpackPlugin(),
],
watchOptions: {
ignored: [ '**/node_modules' ],
diff --git a/tsconfig.json b/tsconfig.json
index d05e883ed70b03..584ac01cb60c71 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -28,6 +28,7 @@
{ "path": "packages/i18n" },
{ "path": "packages/icons" },
{ "path": "packages/interactivity" },
+ { "path": "packages/interactivity-router" },
{ "path": "packages/is-shallow-equal" },
{ "path": "packages/keycodes" },
{ "path": "packages/lazy-import" },