diff --git a/.github/workflows/block-regression.yml b/.github/workflows/block-regression.yml new file mode 100644 index 000000000..8390b2f64 --- /dev/null +++ b/.github/workflows/block-regression.yml @@ -0,0 +1,40 @@ +name: Block Regression + +# Gutenberg ブロックの JS レーン回帰テスト。 +# phpunit.yml(PHP unit/integration)ではカバーされない、ブロックの +# JavaScript 側(block.json + edit/save のパース・シリアライズ健全性)を +# WordPress コア流の fixture ラウンドトリップで検証する。 + +on: + push: + branches: [ master, main, develop ] + pull_request: + branches: [ master, main, develop ] + +jobs: + js-fixtures: + name: Block JS fixtures (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: ['22'] + defaults: + run: + working-directory: blocks + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: npm + cache-dependency-path: blocks/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run block fixture tests + run: npm run test:unit diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 90709cc1d..c76007011 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -35,7 +35,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -54,7 +54,7 @@ jobs: - name: Upload coverage report if: matrix.php-version == '8.3' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: coverage-report path: coverage.xml @@ -89,7 +89,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -98,8 +98,12 @@ jobs: extensions: mbstring, intl, json, mysqli, pdo_mysql tools: composer:v2 - - name: Install dependencies - run: composer install --prefer-dist --no-progress + # 統合レーン専用ツールチェーン(PHPUnit 9.6 + yoast/phpunit-polyfills ^1.1)。 + # WordPress 7.0 テストスイートは PHPUnit 10 で削除された + # parseTestMethodAnnotations() を使うため、テーマ本体の PHPUnit ^11 とは + # 分離した composer manifest からインストールする。 + - name: Install integration test toolchain (PHPUnit 9.6 + polyfills) + run: composer install --working-dir=tests/integration-tooling --prefer-dist --no-progress - name: Install WordPress test suite run: | @@ -153,6 +157,8 @@ jobs: define('WP_TESTS_TITLE', 'Test Blog'); define('WP_PHP_BINARY', 'php'); define('WPLANG', ''); + // 統合ツールチェーンの polyfills を WP テストスイートに明示(読込順に依存しない) + define('WP_TESTS_PHPUNIT_POLYFILLS_PATH', '$GITHUB_WORKSPACE/tests/integration-tooling/vendor/yoast/phpunit-polyfills'); EOF # テーマをWordPressにシンボリックリンク @@ -164,4 +170,4 @@ jobs: - name: Run integration tests env: WP_TESTS_DIR: /tmp/wordpress-tests-lib - run: vendor/bin/phpunit --testsuite integration --colors=always + run: tests/integration-tooling/vendor/bin/phpunit -c phpunit-integration.xml.dist --colors=always diff --git a/.github/workflows/pot_generator.yml b/.github/workflows/pot_generator.yml index 943d6f141..cbdb33813 100644 --- a/.github/workflows/pot_generator.yml +++ b/.github/workflows/pot_generator.yml @@ -24,7 +24,7 @@ jobs: WP_POT_Generator: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Wordpress POT Generation shell: bash run: | diff --git a/.gitignore b/.gitignore index 60f49f4f1..14bccfb7f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,12 @@ claudedocs # Composer vendor/ composer.lock +# 統合テスト用ツールチェーンは再現性のため lock を追跡する +!tests/integration-tooling/composer.lock # PHPUnit .phpunit.cache/ +.phpunit.result.cache tests/coverage/ configs/ diff --git a/bin/run-integration-tests.sh b/bin/run-integration-tests.sh new file mode 100755 index 000000000..125488c2a --- /dev/null +++ b/bin/run-integration-tests.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# +# WordPress 統合テスト(integration レーン)をローカルの Docker で実行する。 +# +# 概要: +# ユニットレーン(PHPUnit 11 / Brain\Monkey)とは分離した統合レーンを、 +# 実 WordPress 環境で実行する。統合レーンは WordPress 7.0 テストスイートが +# 要求する PHPUnit 9.6 + yoast/phpunit-polyfills ^1.1(tests/integration-tooling) +# を使い、bootstrap は WP_UnitTestCase をロードする tests/Integration/bootstrap.php。 +# +# 実行経路は .github/workflows/phpunit.yml の integration-tests ジョブと同一: +# 1. WordPress コア + wordpress-develop テストスイートを取得 +# 2. wp-tests-config.php を生成(テスト用 DB を指す) +# 3. tests/integration-tooling で composer install(PHPUnit 9.6 + polyfills) +# 4. phpunit-integration.xml.dist で integration testsuite を実行 +# +# 前提: +# - Docker / Docker Compose が利用可能なこと +# - テスト用 MySQL は docker/docker-compose.wp7.0-php8.4.yml で起動する +# (初期化 SQL で DB "wordpress_test" を作成済み) +# +# 環境変数(すべて任意・上書き可能): +# WP_VERSION テスト対象の WordPress バージョン (default: 7.0.1) +# COCOON_PHP_IMAGE PHP ランタイムとして使う Docker イメージ (default: wordpress:7.0-php8.4-apache) +# COCOON_COMPOSE_FILE MySQL を起動する compose ファイル +# COCOON_MYSQL_SERVICE compose 内の MySQL サービス名 +# COCOON_DOCKER_NETWORK テスト実行コンテナを接続する Docker ネットワーク +# COCOON_DB_HOST/NAME/USER/PASSWORD テスト用 DB 接続情報 +# COCOON_WORK_VOLUME WP コア/テストスイートを保持する Docker ボリューム名 +# http_proxy/https_proxy 設定されていればコンテナへ引き継ぐ(企業プロキシ等) +# +# 追加引数はそのまま phpunit に渡される。例: +# bin/run-integration-tests.sh --filter NoticeTest +# UPDATE_GOLDEN=1 bin/run-integration-tests.sh --filter NewListBlockRenderTest +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# --- 設定 --- +WP_VERSION="${WP_VERSION:-7.0.1}" +PHP_IMAGE="${COCOON_PHP_IMAGE:-wordpress:7.0-php8.4-apache}" +COMPOSE_FILE="${COCOON_COMPOSE_FILE:-$REPO_DIR/docker/docker-compose.wp7.0-php8.4.yml}" +MYSQL_SERVICE="${COCOON_MYSQL_SERVICE:-mysql-7-0-php84}" +DOCKER_NETWORK="${COCOON_DOCKER_NETWORK:-docker_cocoon-70-php84-network}" +DB_HOST="${COCOON_DB_HOST:-$MYSQL_SERVICE}" +DB_NAME="${COCOON_DB_NAME:-wordpress_test}" +DB_USER="${COCOON_DB_USER:-root}" +DB_PASSWORD="${COCOON_DB_PASSWORD:-rootpassword}" +WORK_VOLUME="${COCOON_WORK_VOLUME:-cocoon_integration_work}" + +COMPOSE="docker compose -f $COMPOSE_FILE" + +echo "== [1/4] テスト用 MySQL を起動 ($MYSQL_SERVICE) ==" +$COMPOSE up -d "$MYSQL_SERVICE" + +echo "== MySQL の healthy を待機 ==" +for _ in $(seq 1 30); do + status="$(docker inspect -f '{{.State.Health.Status}}' "$($COMPOSE ps -q "$MYSQL_SERVICE")" 2>/dev/null || echo starting)" + [ "$status" = "healthy" ] && break + sleep 2 +done +echo " MySQL status: ${status:-unknown}" + +# --- コンテナへ渡すプロキシ設定(任意) --- +PROXY_ARGS=() +if [ -n "${http_proxy:-}" ]; then PROXY_ARGS+=(-e "http_proxy=${http_proxy}"); fi +if [ -n "${https_proxy:-}" ]; then PROXY_ARGS+=(-e "https_proxy=${https_proxy}"); fi +PROXY_ARGS+=(-e "no_proxy=${no_proxy:-$DB_HOST,localhost,127.0.0.1}") + +# --- コンテナ内で実行するセットアップ + テスト --- +# 変数はホスト側で展開して埋め込む。 +INNER=$(cat < "\$WP_TESTS_DIR/wp-tests-config.php" <&1 | tail -5 + +# --- 統合レーン実行 --- +echo "== integration テストを実行 ==" +cd /theme +export WP_TESTS_DIR="\$WP_TESTS_DIR" +exec tests/integration-tooling/vendor/bin/phpunit -c phpunit-integration.xml.dist --colors=always $* +INNER_EOF +) + +echo "== [2/4] PHP ランタイムコンテナ ($PHP_IMAGE) で実行 ==" +docker run --rm \ + --network "$DOCKER_NETWORK" \ + "${PROXY_ARGS[@]}" \ + ${UPDATE_GOLDEN:+-e UPDATE_GOLDEN="$UPDATE_GOLDEN"} \ + -v "$REPO_DIR":/theme \ + -v "$WORK_VOLUME":/work \ + -w /theme \ + "$PHP_IMAGE" \ + bash -c "$INNER" diff --git a/blocks/jest-unit.config.js b/blocks/jest-unit.config.js new file mode 100644 index 000000000..a24be9638 --- /dev/null +++ b/blocks/jest-unit.config.js @@ -0,0 +1,27 @@ +/** + * jest(wp-scripts test-unit-js)用の設定。 + * + * @wordpress/scripts の既定プリセット(@wordpress/jest-preset-default)を土台に、 + * Cocoon 固有のグローバルスタブ(setup-cocoon-globals.js)を setupFiles に追加し、 + * 本番ビルドで外部化される `@wordpress/editor` をテスト用スタブに解決する。 + */ +const path = require( 'path' ); +const defaultConfig = require( '@wordpress/scripts/config/jest-unit.config.js' ); +const presetSetupFiles = + require( '@wordpress/jest-preset-default/jest-preset.js' ).setupFiles || []; + +module.exports = { + ...defaultConfig, + setupFiles: [ + ...presetSetupFiles, + require.resolve( './jest/setup-cocoon-globals.js' ), + ], + moduleNameMapper: { + ...( defaultConfig.moduleNameMapper || {} ), + // 本番では wp.editor グローバルに外部化される(devDep に無い)。 + '^@wordpress/editor$': path.resolve( + __dirname, + 'jest/stubs/wordpress-editor.js' + ), + }, +}; diff --git a/blocks/jest/block-registry.js b/blocks/jest/block-registry.js new file mode 100644 index 000000000..047407b0a --- /dev/null +++ b/blocks/jest/block-registry.js @@ -0,0 +1,164 @@ +/** + * Cocoon ブロックの自動発見・一括登録ハーネス。 + * + * `blocks.js`(本番の登録経路)は `unstable__bootstrapServerSideBlockDefinitions` + * や大量の `wp.*` グローバル副作用に依存しており、テストには不向き。そこで本 + * モジュールは、各ブロックの `index.js` が公開する `{ metadata, name, settings }` + * を直接 import して `registerBlockType` する、テスト専用の登録経路を提供する。 + * + * 発見方法(block.json 自動発見型): + * src/{block,block-universal,micro}// に block.json と index.js が + * 両方存在するディレクトリを構造化ブロックとみなす。 + * + * innerBlocks(core/paragraph, core/list など)の忠実なシリアライズのため、 + * @wordpress/block-library の registerCoreBlocks() でコアブロックも登録する。 + */ +const fs = require( 'fs' ); +const path = require( 'path' ); + +const { + registerBlockType, + getBlockType, + setCategories, + getCategories, +} = require( '@wordpress/blocks' ); +const { registerCoreBlocks } = require( '@wordpress/block-library' ); + +const SRC_DIR = path.join( __dirname, '..', 'src' ); +const TARGET_DIRS = [ 'block', 'block-universal', 'micro' ]; + +// block.json の category は既定カテゴリーに含まれないため補う。 +const COCOON_CATEGORIES = [ + { slug: 'cocoon-block', title: 'Cocoon' }, + { slug: 'cocoon-universal-block', title: 'Cocoon Universal' }, + { slug: 'cocoon-micro', title: 'Cocoon Micro' }, +]; + +/** + * ディスク上に存在するが本ハーネスの対象外とするブロックと、その理由。 + * blocks.js の登録配列に含まれない(=本番でも未登録の)ブロックはここで除外する。 + */ +const EXCLUDED = { + 'comparison-box': + 'blocks.js の登録配列に含まれず本番でも未登録。加えて、blocks.js から import されない子ブロック cocoon-blocks/comparison-left・comparison-right(items/block.js)に依存する。', +}; + +/** + * 対象ディレクトリを走査し、block.json と index.js を持つブロックを列挙する。 + * + * @return {{dir:string, slug:string, indexPath:string, blockJsonPath:string}[]} + */ +function discoverBlockDirs() { + const found = []; + for ( const dir of TARGET_DIRS ) { + const base = path.join( SRC_DIR, dir ); + if ( ! fs.existsSync( base ) ) { + continue; + } + for ( const slug of fs.readdirSync( base ).sort() ) { + const dirPath = path.join( base, slug ); + if ( ! fs.statSync( dirPath ).isDirectory() ) { + continue; + } + const indexPath = path.join( dirPath, 'index.js' ); + const blockJsonPath = path.join( dirPath, 'block.json' ); + if ( + fs.existsSync( indexPath ) && + fs.existsSync( blockJsonPath ) + ) { + found.push( { dir, slug, indexPath, blockJsonPath } ); + } + } + } + return found; +} + +/** + * 各ブロックの index.js を読み込む。ロード時例外(重い外部依存など)は + * 失敗として捕捉し、除外理由付きで返す。 + * + * @return {{loaded:Object[], excluded:Object[]}} + */ +function loadCocoonBlocks() { + const loaded = []; + const excluded = []; + for ( const info of discoverBlockDirs() ) { + if ( EXCLUDED[ info.slug ] ) { + excluded.push( { ...info, reason: EXCLUDED[ info.slug ] } ); + continue; + } + try { + // eslint-disable-next-line import/no-dynamic-require + const mod = require( info.indexPath ); + const { metadata, name, settings } = mod; + if ( ! metadata || ! name || ! settings ) { + throw new Error( + 'index.js が { metadata, name, settings } を公開していない' + ); + } + loaded.push( { ...info, metadata, name, settings } ); + } catch ( e ) { + excluded.push( { + ...info, + reason: `index.js のロードに失敗: ${ e.message }`, + } ); + } + } + return { loaded, excluded }; +} + +let registrationResult = null; + +/** + * コアブロック+発見した全 Cocoon ブロックを登録する(冪等)。 + * + * @return {{registered:Object[], excluded:Object[]}} + * registered: 登録に成功したブロック({slug,name,blockJsonPath,...}) + * excluded: 対象外・ロード失敗・登録失敗のブロックと理由 + */ +function registerAllBlocks() { + if ( registrationResult ) { + return registrationResult; + } + + // カテゴリー補完(既存を保持しつつ不足分を追加)。 + const existing = getCategories(); + const existingSlugs = new Set( existing.map( ( c ) => c.slug ) ); + setCategories( [ + ...existing, + ...COCOON_CATEGORIES.filter( ( c ) => ! existingSlugs.has( c.slug ) ), + ] ); + + // innerBlocks の忠実化のためコアブロックを登録。 + registerCoreBlocks(); + + const { loaded, excluded } = loadCocoonBlocks(); + const registered = []; + const registerExcluded = [ ...excluded ]; + + for ( const b of loaded ) { + try { + if ( ! getBlockType( b.name ) ) { + // balloon の PoC と同様、metadata を第1引数に渡して登録する。 + registerBlockType( b.metadata, b.settings ); + } + registered.push( b ); + } catch ( e ) { + registerExcluded.push( { + ...b, + reason: `registerBlockType に失敗: ${ e.message }`, + } ); + } + } + + registrationResult = { registered, excluded: registerExcluded }; + return registrationResult; +} + +module.exports = { + SRC_DIR, + TARGET_DIRS, + discoverBlockDirs, + loadCocoonBlocks, + registerAllBlocks, +}; diff --git a/blocks/jest/setup-cocoon-globals.js b/blocks/jest/setup-cocoon-globals.js new file mode 100644 index 000000000..0923dd468 --- /dev/null +++ b/blocks/jest/setup-cocoon-globals.js @@ -0,0 +1,68 @@ +/** + * Cocoon ブロックのテスト用グローバル定義。 + * + * Cocoon のブロックソースは、WordPress 管理画面から `wp_localize_script` 等で + * 注入されるグローバル変数(`wp`, `gbSettings`, `gbSpeechBalloons` など)に + * モジュール読み込み時点で依存している。jest(jsdom)にはこれらが存在しないため、 + * ここで最小限かつ決定的なスタブを定義する。 + * + * これは実行前セットアップ(jest の setupFiles)であり、テスト対象モジュールが + * import される前に評価される必要がある。golden(フィクスチャ)を安定させるため、 + * すべてのスタブは固定値にする(乱数・時刻・環境依存の値を含めない)。 + * + * 対象ブロック(src/block, src/block-universal, src/micro)がモジュールロード時に + * 参照するグローバルの和集合をカバーする。 + */ + +// --- wp.* 名前空間 --------------------------------------------------------- +// Cocoon のブロックは一部の依存を ESM import ではなく `wp.*` グローバル経由で +// 参照する。これらは deprecated.js / save.js / edit.js のモジュールトップレベルで +// 分割代入されるため、未定義だと import 時点で ReferenceError になる。 +const blocks = require( '@wordpress/blocks' ); +const components = require( '@wordpress/components' ); +const element = require( '@wordpress/element' ); + +global.wp = global.wp || {}; +// transforms.js が `const { createBlock } = wp.blocks;` をトップレベルで実行する。 +global.wp.blocks = blocks; +// icon-box/info-box/blogcard/sticky-box の deprecated.js が +// `const { PanelBody, SelectControl } = wp.components;` をトップレベルで実行する。 +global.wp.components = components; +// tab/save.js の `const { RawHTML } = wp.element;` と +// timeline-item/deprecated.js の `const { Fragment } = wp.element;` 用。 +global.wp.element = element; + +// radar/edit.js はモジュールトップレベルで `wp.data.subscribe(...)` を呼ぶ。 +// 実ストアの副作用(registerCoreBlocks のディスパッチで購読コールバックが発火し、 +// 未登録の core/block-editor ストアに触れて例外になる)を避けるため、 +// wp.data は決定的なノーオペスタブにする。edit のレンダリングは行わないため、 +// select は空のブロック一覧を返すだけでよい。 +global.wp.data = global.wp.data || { + subscribe: () => () => {}, + select: () => ( { + getBlocks: () => [], + getBlockRootClientId: () => null, + getCurrentPostType: () => null, + } ), + dispatch: () => ( {} ), +}; + +// --- gb* ローカライズ変数 -------------------------------------------------- +// ほとんどは edit 内で `typeof gbXxx !== 'undefined'` ガード済みだが、 +// 一部(balloon/edit.js の gbSettings / gbSpeechBalloons)はトップレベルで +// 参照される。決定性のためすべて固定の空値で定義する。 +global.gbSettings = global.gbSettings || {}; +global.gbSpeechBalloons = global.gbSpeechBalloons || []; +global.gbColors = global.gbColors || { keyColor: '#19448e' }; +global.gbCodeLanguages = global.gbCodeLanguages || []; +global.gbTemplates = global.gbTemplates || []; +global.gbItemRankings = global.gbItemRankings || []; +global.gbNavMenus = global.gbNavMenus || []; +global.gbUsers = global.gbUsers || []; + +// amazon-product-link / rakuten-product-link は window 経由で参照する。 +global.window = global.window || global; +global.window.gbAmazonBlockDefaults = + global.window.gbAmazonBlockDefaults || {}; +global.window.gbRakutenBlockDefaults = + global.window.gbRakutenBlockDefaults || {}; diff --git a/blocks/jest/stubs/wordpress-editor.js b/blocks/jest/stubs/wordpress-editor.js new file mode 100644 index 000000000..29918fa8d --- /dev/null +++ b/blocks/jest/stubs/wordpress-editor.js @@ -0,0 +1,18 @@ +/** + * `@wordpress/editor` のテスト用スタブ。 + * + * 本番ビルドでは wp-scripts が `@wordpress/editor` を外部化し、実行時に + * グローバル `wp.editor` へ解決する(依存 package.json には含まれない)。 + * テストでは実体が無いため、ブロックがモジュールロード時に import する + * 名前付きエクスポートのみを最小スタブで供給する。 + * + * 利用箇所: 8 ブロック(cta/info-list/navicard/new-list/popular-list/ + * profile/ranking/template)が `ServerSideRender` を edit プレビューで使う。 + * save/serialize には一切関与しないため、フィクスチャの決定性に影響しない。 + */ +const ServerSideRender = () => null; + +module.exports = { + ServerSideRender, + default: ServerSideRender, +}; diff --git a/blocks/package-lock.json b/blocks/package-lock.json index 51b3ec21a..c0d8141b0 100644 --- a/blocks/package-lock.json +++ b/blocks/package-lock.json @@ -20,6 +20,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@wordpress/api-fetch": "^6.41.0", "@wordpress/block-editor": "^12.12.0", + "@wordpress/block-library": "^8.35.0", "@wordpress/compose": "^6.21.0", "@wordpress/data": "^9.14.0", "@wordpress/dom-ready": "^3.44.0", @@ -4690,6 +4691,34 @@ "dev": true, "license": "MIT" }, + "node_modules/@preact/signals": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.4.tgz", + "integrity": "sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact": "10.x" + } + }, + "node_modules/@preact/signals-core": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.4.tgz", + "integrity": "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/@prisma/instrumentation": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.11.1.tgz", @@ -6018,6 +6047,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/simple-peer": { + "version": "9.11.9", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.9.tgz", + "integrity": "sha512-6Gdl7TSS5oh9nuwKD4Pl8cSmaxWycYeZz9HLnJBNvIwWjZuGVsmHe9RwW3+9RxfhC1aIR9Z83DvaJoMw6rhkbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -7207,6 +7246,65 @@ "react-dom": "^18.0.0" } }, + "node_modules/@wordpress/block-library": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.35.0.tgz", + "integrity": "sha512-Cb0n8gh3wK+aFwbPjhyrJpHoe7mqszi8FsxJtw9e37lxwax323pdc5G2YcsfEpfGs7fJnoJ9CA8GfKPqzpdVAw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/a11y": "^3.58.0", + "@wordpress/api-fetch": "^6.55.0", + "@wordpress/autop": "^3.58.0", + "@wordpress/blob": "^3.58.0", + "@wordpress/block-editor": "^12.26.0", + "@wordpress/blocks": "^12.35.0", + "@wordpress/components": "^27.6.0", + "@wordpress/compose": "^6.35.0", + "@wordpress/core-data": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/date": "^4.58.0", + "@wordpress/deprecated": "^3.58.0", + "@wordpress/dom": "^3.58.0", + "@wordpress/element": "^5.35.0", + "@wordpress/escape-html": "^2.58.0", + "@wordpress/hooks": "^3.58.0", + "@wordpress/html-entities": "^3.58.0", + "@wordpress/i18n": "^4.58.0", + "@wordpress/icons": "^9.49.0", + "@wordpress/interactivity": "^5.7.0", + "@wordpress/interactivity-router": "^1.8.0", + "@wordpress/keyboard-shortcuts": "^4.35.0", + "@wordpress/keycodes": "^3.58.0", + "@wordpress/notices": "^4.26.0", + "@wordpress/patterns": "^1.19.0", + "@wordpress/primitives": "^3.56.0", + "@wordpress/private-apis": "^0.40.0", + "@wordpress/reusable-blocks": "^4.35.0", + "@wordpress/rich-text": "^6.35.0", + "@wordpress/server-side-render": "^4.35.0", + "@wordpress/url": "^3.59.0", + "@wordpress/viewport": "^5.35.0", + "@wordpress/wordcount": "^3.58.0", + "change-case": "^4.1.2", + "clsx": "^2.1.1", + "colord": "^2.7.0", + "escape-html": "^1.0.3", + "fast-average-color": "^9.1.1", + "fast-deep-equal": "^3.1.3", + "memize": "^2.1.0", + "remove-accents": "^0.5.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/block-serialization-default-parser": { "version": "4.58.0", "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.58.0.tgz", @@ -7390,6 +7488,43 @@ "react": "^18.0.0" } }, + "node_modules/@wordpress/core-data": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.35.0.tgz", + "integrity": "sha512-VnESF55nkAkKHQVwj0Oo8AU6w/yWjg/RQb+wKqJRnDuHEKHDAF5PI+9lYmcAsbXdnAc2yyf3lwBGk4w8m4uxlA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/api-fetch": "^6.55.0", + "@wordpress/block-editor": "^12.26.0", + "@wordpress/blocks": "^12.35.0", + "@wordpress/compose": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/deprecated": "^3.58.0", + "@wordpress/element": "^5.35.0", + "@wordpress/html-entities": "^3.58.0", + "@wordpress/i18n": "^4.58.0", + "@wordpress/is-shallow-equal": "^4.58.0", + "@wordpress/private-apis": "^0.40.0", + "@wordpress/rich-text": "^6.35.0", + "@wordpress/sync": "^0.20.0", + "@wordpress/undo-manager": "^0.18.0", + "@wordpress/url": "^3.59.0", + "change-case": "^4.1.2", + "equivalent-key-map": "^0.2.2", + "fast-deep-equal": "^3.1.3", + "memize": "^2.1.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/data": { "version": "9.28.0", "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.28.0.tgz", @@ -7681,6 +7816,34 @@ "node": ">=12" } }, + "node_modules/@wordpress/interactivity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-5.7.0.tgz", + "integrity": "sha512-zB2CZwj4TWpE5OJtFfpma1OYvrsLTMgWpRJ2ojRuNqBruZfQAUqOXsn6JyTTWs6bgyd4dr/WOZay4Wr4isleUw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@preact/signals": "^1.2.2", + "deepsignal": "^1.4.0", + "preact": "^10.19.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/interactivity-router": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-1.8.0.tgz", + "integrity": "sha512-i2Lb9008DmPyh/DyCWhvVkwUha2zhLqPfBtMAUQuwes2prDLyl8FnWVS6bwXXYpu6qKkLhFz6hzXt3O988u53Q==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/interactivity": "^5.7.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wordpress/is-shallow-equal": { "version": "4.58.0", "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.58.0.tgz", @@ -7796,6 +7959,37 @@ "npm-package-json-lint": ">=6.0.0" } }, + "node_modules/@wordpress/patterns": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.19.0.tgz", + "integrity": "sha512-L0NhNn6P2YuZRgZNlZP0IFYKoVjQI5Vf/4edgG8yjYwuXLkanJvBZWglswu4Z8jzlsBN72o0n3K37FO8VzcnmA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/a11y": "^3.58.0", + "@wordpress/block-editor": "^12.26.0", + "@wordpress/blocks": "^12.35.0", + "@wordpress/components": "^27.6.0", + "@wordpress/compose": "^6.35.0", + "@wordpress/core-data": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/element": "^5.35.0", + "@wordpress/html-entities": "^3.58.0", + "@wordpress/i18n": "^4.58.0", + "@wordpress/icons": "^9.49.0", + "@wordpress/notices": "^4.26.0", + "@wordpress/private-apis": "^0.40.0", + "@wordpress/url": "^3.59.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/postcss-plugins-preset": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.41.0.tgz", @@ -7917,6 +8111,34 @@ "redux": ">=4" } }, + "node_modules/@wordpress/reusable-blocks": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.35.0.tgz", + "integrity": "sha512-vofZGdVCOljSviar11sJWK+8loVAz53fBqPllcC0MbnSWkj4VPF4L6VWFVus1PQyL2MdkHynWRce9MHpKvN1NQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/block-editor": "^12.26.0", + "@wordpress/blocks": "^12.35.0", + "@wordpress/components": "^27.6.0", + "@wordpress/core-data": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/element": "^5.35.0", + "@wordpress/i18n": "^4.58.0", + "@wordpress/icons": "^9.49.0", + "@wordpress/notices": "^4.26.0", + "@wordpress/private-apis": "^0.40.0", + "@wordpress/url": "^3.59.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/rich-text": { "version": "6.35.0", "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.35.0.tgz", @@ -8059,6 +8281,33 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@wordpress/server-side-render": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.35.0.tgz", + "integrity": "sha512-yTbq31iGFc9VaRMtdCLlyZtbwCN+WdiECEqY6MsMXf31/TDmUcdZPxRTORy3Rz2HxEpFfUMjpRz7A8wn1QZmzA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/api-fetch": "^6.55.0", + "@wordpress/blocks": "^12.35.0", + "@wordpress/components": "^27.6.0", + "@wordpress/compose": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/deprecated": "^3.58.0", + "@wordpress/element": "^5.35.0", + "@wordpress/i18n": "^4.58.0", + "@wordpress/url": "^3.59.0", + "fast-deep-equal": "^3.1.3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@wordpress/shortcode": { "version": "3.58.0", "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.58.0.tgz", @@ -8108,6 +8357,28 @@ "stylelint-scss": "^6.4.0" } }, + "node_modules/@wordpress/sync": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.20.0.tgz", + "integrity": "sha512-nEB2UHiSF0PoU5irAmeuljt4OQbqKHpOlJOb0WMiydbDBGTzquxF6I61ax2mFTbSDntL0AUt8pxi6eT9con1sQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@types/simple-peer": "^9.11.5", + "@wordpress/url": "^3.59.0", + "import-locals": "^2.0.0", + "lib0": "^0.2.42", + "simple-peer": "^9.11.0", + "y-indexeddb": "~9.0.11", + "y-protocols": "^1.0.5", + "y-webrtc": "~10.2.5", + "yjs": "~13.6.6" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wordpress/theme": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.8.0.tgz", @@ -8218,6 +8489,25 @@ "node": ">=12" } }, + "node_modules/@wordpress/viewport": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.35.0.tgz", + "integrity": "sha512-iCIVbFcA1CzGXEVu5COoi6DRdh3m3zEMWOYKKqF1C7W/LhZnTNRsHOZbHq0tv7MOkwx0Nl7+hDJlPRM0B3fb7A==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/compose": "^6.35.0", + "@wordpress/data": "^9.28.0", + "@wordpress/element": "^5.35.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@wordpress/warning": { "version": "2.58.0", "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.58.0.tgz", @@ -10986,6 +11276,33 @@ "node": ">=0.10.0" } }, + "node_modules/deepsignal": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.6.0.tgz", + "integrity": "sha512-oplhhOSfBRKmx96B0PzbhRaX2W7iDpH6BEqvyGLIZPO2pdHQD+/P3u4Z8wi/sKXjCE7ht8C7ULTEzLSJbgwccA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@preact/signals": "^1.1.4 || ^2.0.0", + "@preact/signals-core": "^1.5.1", + "@preact/signals-react": "^1.3.8 || ^2.0.0 || ^3.0.0", + "preact": "^10.16.0" + }, + "peerDependenciesMeta": { + "@preact/signals": { + "optional": true + }, + "@preact/signals-core": { + "optional": true + }, + "@preact/signals-react": { + "optional": true + }, + "preact": { + "optional": true + } + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -11575,6 +11892,13 @@ "dev": true, "license": "MIT" }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -12968,6 +13292,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fast-average-color": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/fast-average-color/-/fast-average-color-9.5.2.tgz", + "integrity": "sha512-FbaU8iPTPljP7tmnVhXbCyASNw/zxnmaNDf88gn5pTXlNvejl9w4FapeWMh6UNDwIjhJJU28EPfQWwW032YgPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -13662,6 +13996,13 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -14668,6 +15009,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-locals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-locals/-/import-locals-2.0.0.tgz", + "integrity": "sha512-1/bPE89IZhyf7dr5Pkz7b4UyVXy5pEt7PTEfye15UEn3AK8+2zwcDCfKk9Pwun4ltfhOSszOrReSsFcDKw/yoA==", + "dev": true, + "license": "MIT" + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -15442,6 +15790,17 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -16670,6 +17029,28 @@ "node": ">= 0.8.0" } }, + "node_modules/lib0": { + "version": "0.2.117", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lighthouse": { "version": "12.8.2", "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.8.2.tgz", @@ -19560,6 +19941,25 @@ "node": ">=0.10.0" } }, + "node_modules/preact": { + "version": "10.29.7", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", + "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -21631,6 +22031,61 @@ "dev": true, "license": "MIT" }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -24742,6 +25197,76 @@ "node": ">=0.4" } }, + "node_modules/y-indexeddb": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.12.tgz", + "integrity": "sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lib0": "^0.2.74" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-protocols": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", + "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lib0": "^0.2.85" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-webrtc": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.6.tgz", + "integrity": "sha512-1kZ4YYwksFZi8+l8mTebVX9vW6Q5MnqxMkvNU700X5dBE38usurt/JgeXSIQRpK3NwUYYb9y63Jn9FMpMH6/vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lib0": "^0.2.42", + "simple-peer": "^9.11.0", + "y-protocols": "^1.0.6" + }, + "bin": { + "y-webrtc-signaling": "bin/server.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "optionalDependencies": { + "ws": "^8.14.2" + }, + "peerDependencies": { + "yjs": "^13.6.8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -24809,6 +25334,24 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yjs": { + "version": "13.6.31", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.31.tgz", + "integrity": "sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/blocks/package.json b/blocks/package.json index ed1676600..0a1474393 100644 --- a/blocks/package.json +++ b/blocks/package.json @@ -13,7 +13,8 @@ "lint:php:fix": "phpcbf -d memory_limit=512M --standard=WordPress --ignore=*/node_modules/* --extensions=php .", "format": "wp-scripts format", "format:check": "wp-scripts format --check", - "start": "wp-scripts start" + "start": "wp-scripts start", + "test:unit": "wp-scripts test-unit-js" }, "devDependencies": { "@babel/core": "^7.23.2", @@ -24,6 +25,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@wordpress/api-fetch": "^6.41.0", "@wordpress/block-editor": "^12.12.0", + "@wordpress/block-library": "^8.35.0", "@wordpress/compose": "^6.21.0", "@wordpress/data": "^9.14.0", "@wordpress/dom-ready": "^3.44.0", diff --git a/blocks/src/block/balloon/test/balloon.test.js b/blocks/src/block/balloon/test/balloon.test.js new file mode 100644 index 000000000..659290c6f --- /dev/null +++ b/blocks/src/block/balloon/test/balloon.test.js @@ -0,0 +1,90 @@ +/** + * balloon ブロックのフィクスチャ回帰テスト。 + * + * WordPress コアの "full-content" フィクスチャ方式に倣い、シリアライズ済みの + * ブロック HTML(fixtures/__.html)を検証する: + * 1. parse() でブロックにパースできる + * 2. isValid === true(save 出力と保存済み HTML が一致する = デシリアライズ健全性) + * 3. validationIssues が空 + * 4. serialize() で元の HTML にラウンドトリップする(再シリアライズ安定性) + * + * これにより save.js / block.json / deprecated.js の変更が保存形を壊した場合に + * 検知できる、JS 側回帰安全網の骨格となる。 + */ +import { readFileSync } from 'fs'; +import path from 'path'; + +import { + registerBlockType, + parse, + serialize, + setCategories, + getCategories, +} from '@wordpress/blocks'; +import { RichText } from '@wordpress/block-editor'; + +import { metadata, name, settings } from '../index'; + +const FIXTURES_DIR = path.join( __dirname, 'fixtures' ); + +const readFixture = ( variant ) => + readFileSync( + path.join( FIXTURES_DIR, `balloon__${ variant }.html` ), + 'utf8' + ).trim(); + +beforeAll( () => { + // block.json の category "cocoon-block" はテスト環境では未登録なので追加する。 + setCategories( [ + ...getCategories(), + { slug: 'cocoon-block', title: 'Cocoon' }, + ] ); + + // innerBlocks に使う core/paragraph の最小スタブ。 + // フィクスチャ内の

の保存形はこのスタブが基準となる。 + registerBlockType( 'core/paragraph', { + title: 'Paragraph', + edit: () => null, + category: 'text', + attributes: { + content: { + type: 'string', + source: 'html', + selector: 'p', + default: '', + }, + }, + save: ( { attributes } ) => ( + + ), + } ); + + // テスト対象。不安定 API(blocks.js の bootstrap)を避け、 + // index.js の export を使って直接登録する。 + registerBlockType( metadata, settings ); +} ); + +describe( 'balloon block fixtures', () => { + it.each( [ 'default', 'with-inner-blocks' ] )( + '%s: パースすると単一の有効な balloon ブロックになる', + ( variant ) => { + const html = readFixture( variant ); + const blocks = parse( html ); + + expect( blocks ).toHaveLength( 1 ); + expect( blocks[ 0 ].name ).toBe( name ); + expect( blocks[ 0 ].isValid ).toBe( true ); + expect( blocks[ 0 ].validationIssues ).toEqual( [] ); + } + ); + + it.each( [ 'default', 'with-inner-blocks' ] )( + '%s: 再シリアライズで元の HTML にラウンドトリップする', + ( variant ) => { + const html = readFixture( variant ); + const blocks = parse( html ); + + expect( serialize( blocks ) ).toBe( html ); + } + ); +} ); diff --git a/blocks/src/block/balloon/test/fixtures/balloon__default.html b/blocks/src/block/balloon/test/fixtures/balloon__default.html new file mode 100644 index 000000000..3deed7b13 --- /dev/null +++ b/blocks/src/block/balloon/test/fixtures/balloon__default.html @@ -0,0 +1,3 @@ + +

+ diff --git a/blocks/src/block/balloon/test/fixtures/balloon__with-inner-blocks.html b/blocks/src/block/balloon/test/fixtures/balloon__with-inner-blocks.html new file mode 100644 index 000000000..c8ab1d1da --- /dev/null +++ b/blocks/src/block/balloon/test/fixtures/balloon__with-inner-blocks.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod!

+
+ diff --git a/blocks/src/test/block-fixtures.test.js b/blocks/src/test/block-fixtures.test.js new file mode 100644 index 000000000..30e13a265 --- /dev/null +++ b/blocks/src/test/block-fixtures.test.js @@ -0,0 +1,89 @@ +/** + * 全 Cocoon 構造化ブロックのフィクスチャ回帰テスト(汎用ハーネス)。 + * + * WordPress コアの "full-content" フィクスチャ方式に倣い、block.json 自動発見で + * 登録した各ブロックについて、シリアライズ済みの保存 HTML + * (fixtures/__default.html)を検証する: + * 1. parse() で単一ブロックにパースできる + * 2. name が block.json の name と一致する + * 3. isValid === true(save 出力と保存済み HTML が一致 = デシリアライズ健全性) + * 4. validationIssues が空 + * 5. serialize() で元の HTML にラウンドトリップする(再シリアライズ安定性) + * + * これにより、いずれかのブロックの save.js / block.json / deprecated.js の変更が + * 保存形を壊した場合に、個別テストを書かずとも 1 ファイルで検知できる。 + * + * フィクスチャは手書き当て推量ではなく、createBlock / getBlockFromExample → + * serialize で実保存形をキャプチャして生成している(jest/block-registry.js と + * scratchpad の generate-fixtures.js を参照)。 + */ +import { readFileSync } from 'fs'; +import path from 'path'; + +import { parse, serialize } from '@wordpress/blocks'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { registerAllBlocks } = require( '../../jest/block-registry' ); + +const FIXTURES_DIR = path.join( __dirname, 'fixtures' ); + +const readFixture = ( slug ) => + readFileSync( + path.join( FIXTURES_DIR, `${ slug }__default.html` ), + 'utf8' + ).trim(); + +// core/list など一部のコアブロック save は子要素に key を付けずにレンダリングする。 +// serialize(内部で renderToString)時に React の dev 警告が出るが、保存出力の +// 正しさとは無関係で決定的。@wordpress/jest-console が console.error で失敗するのを +// 避けるため、この 1 種のみを絞って抑制する(他の console.error は従来どおり失敗)。 +// +// jest-console は setupFilesAfterEnv の beforeEach で毎テスト console.error を +// 張り替えるため、こちらの beforeEach(後に登録=後に実行)で再ラップする。 +let forwardError; +beforeEach( () => { + forwardError = console.error; + jest.spyOn( console, 'error' ).mockImplementation( ( ...args ) => { + const message = String( args[ 0 ] ?? '' ); + if ( message.includes( 'unique "key" prop' ) ) { + return; + } + forwardError( ...args ); + } ); +} ); + +afterEach( () => { + if ( console.error.mockRestore ) { + console.error.mockRestore(); + } +} ); + +const { registered } = registerAllBlocks(); + +// describe.each 用の [表示名, ブロック定義] タプル。 +const cases = registered.map( ( b ) => [ b.slug, b ] ); + +describe( 'Cocoon ブロックのフィクスチャ回帰(全登録ブロック)', () => { + it( '対象ブロックが 1 つ以上発見・登録されている', () => { + expect( registered.length ).toBeGreaterThan( 0 ); + } ); + + describe.each( cases )( '%s', ( slug, block ) => { + it( 'パースすると単一の有効なブロックになる', () => { + const html = readFixture( slug ); + const blocks = parse( html ); + + expect( blocks ).toHaveLength( 1 ); + expect( blocks[ 0 ].name ).toBe( block.name ); + expect( blocks[ 0 ].isValid ).toBe( true ); + expect( blocks[ 0 ].validationIssues ).toEqual( [] ); + } ); + + it( '再シリアライズで元の HTML にラウンドトリップする', () => { + const html = readFixture( slug ); + const blocks = parse( html ); + + expect( serialize( blocks ) ).toBe( html ); + } ); + } ); +} ); diff --git a/blocks/src/test/fixtures/ad__default.html b/blocks/src/test/fixtures/ad__default.html new file mode 100644 index 000000000..5cb990408 --- /dev/null +++ b/blocks/src/test/fixtures/ad__default.html @@ -0,0 +1,3 @@ + +
[ad]
+ diff --git a/blocks/src/test/fixtures/amazon-product-link__default.html b/blocks/src/test/fixtures/amazon-product-link__default.html new file mode 100644 index 000000000..19955a6e1 --- /dev/null +++ b/blocks/src/test/fixtures/amazon-product-link__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/balloon__default.html b/blocks/src/test/fixtures/balloon__default.html new file mode 100644 index 000000000..54d43a9c9 --- /dev/null +++ b/blocks/src/test/fixtures/balloon__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod!

+
+ diff --git a/blocks/src/test/fixtures/blank-box__default.html b/blocks/src/test/fixtures/blank-box__default.html new file mode 100644 index 000000000..d4dea1ad0 --- /dev/null +++ b/blocks/src/test/fixtures/blank-box__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/blogcard__default.html b/blocks/src/test/fixtures/blogcard__default.html new file mode 100644 index 000000000..9a3c2aaf4 --- /dev/null +++ b/blocks/src/test/fixtures/blogcard__default.html @@ -0,0 +1,5 @@ + +
+https://wp-cocoon.com/ +
+ diff --git a/blocks/src/test/fixtures/box-menu__default.html b/blocks/src/test/fixtures/box-menu__default.html new file mode 100644 index 000000000..edb971b7b --- /dev/null +++ b/blocks/src/test/fixtures/box-menu__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/button-wrap__default.html b/blocks/src/test/fixtures/button-wrap__default.html new file mode 100644 index 000000000..72cf58a33 --- /dev/null +++ b/blocks/src/test/fixtures/button-wrap__default.html @@ -0,0 +1,3 @@ + + + diff --git a/blocks/src/test/fixtures/button__default.html b/blocks/src/test/fixtures/button__default.html new file mode 100644 index 000000000..dbfe45a07 --- /dev/null +++ b/blocks/src/test/fixtures/button__default.html @@ -0,0 +1,3 @@ + + + diff --git a/blocks/src/test/fixtures/campaign__default.html b/blocks/src/test/fixtures/campaign__default.html new file mode 100644 index 000000000..fcc2c8b2a --- /dev/null +++ b/blocks/src/test/fixtures/campaign__default.html @@ -0,0 +1,5 @@ + +
+

キャンペーン期間中のみ表示されるコンテンツです。

+
+ diff --git a/blocks/src/test/fixtures/caption-box__default.html b/blocks/src/test/fixtures/caption-box__default.html new file mode 100644 index 000000000..cbe5e380d --- /dev/null +++ b/blocks/src/test/fixtures/caption-box__default.html @@ -0,0 +1,5 @@ + +
Lorem ipsum dolor
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/cta__default.html b/blocks/src/test/fixtures/cta__default.html new file mode 100644 index 000000000..ee2ee8474 --- /dev/null +++ b/blocks/src/test/fixtures/cta__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/faq__default.html b/blocks/src/test/fixtures/faq__default.html new file mode 100644 index 000000000..5aa25c5d2 --- /dev/null +++ b/blocks/src/test/fixtures/faq__default.html @@ -0,0 +1,5 @@ + +
Q
Lorem ipsum dolor sit amet ?
A
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/icon-box__default.html b/blocks/src/test/fixtures/icon-box__default.html new file mode 100644 index 000000000..20d90d514 --- /dev/null +++ b/blocks/src/test/fixtures/icon-box__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/icon-list__default.html b/blocks/src/test/fixtures/icon-list__default.html new file mode 100644 index 000000000..0c5629ba3 --- /dev/null +++ b/blocks/src/test/fixtures/icon-list__default.html @@ -0,0 +1,23 @@ + +
Title
+
    +
  • Lorem ipsum dolor sit amet.
  • + + + +
  • Lorem ipsum dolor sit amet.
  • + + + +
  • Lorem ipsum dolor sit amet.
  • + + + +
  • Lorem ipsum dolor sit amet.
  • + + + +
  • Lorem ipsum dolor sit amet.
  • +
+
+ diff --git a/blocks/src/test/fixtures/info-box__default.html b/blocks/src/test/fixtures/info-box__default.html new file mode 100644 index 000000000..5cf75cba6 --- /dev/null +++ b/blocks/src/test/fixtures/info-box__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/info-list__default.html b/blocks/src/test/fixtures/info-list__default.html new file mode 100644 index 000000000..9b914a5e9 --- /dev/null +++ b/blocks/src/test/fixtures/info-list__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/label-box__default.html b/blocks/src/test/fixtures/label-box__default.html new file mode 100644 index 000000000..018b3ef72 --- /dev/null +++ b/blocks/src/test/fixtures/label-box__default.html @@ -0,0 +1,5 @@ + +
Lorem ipsum dolor
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/login-user-only__default.html b/blocks/src/test/fixtures/login-user-only__default.html new file mode 100644 index 000000000..c8f3a3213 --- /dev/null +++ b/blocks/src/test/fixtures/login-user-only__default.html @@ -0,0 +1,3 @@ + + + diff --git a/blocks/src/test/fixtures/micro-balloon__default.html b/blocks/src/test/fixtures/micro-balloon__default.html new file mode 100644 index 000000000..f63d23d48 --- /dev/null +++ b/blocks/src/test/fixtures/micro-balloon__default.html @@ -0,0 +1,3 @@ + +
microcopy balloon
+ diff --git a/blocks/src/test/fixtures/micro-text__default.html b/blocks/src/test/fixtures/micro-text__default.html new file mode 100644 index 000000000..fc16ab89e --- /dev/null +++ b/blocks/src/test/fixtures/micro-text__default.html @@ -0,0 +1,3 @@ + +
microcopy text
+ diff --git a/blocks/src/test/fixtures/navicard__default.html b/blocks/src/test/fixtures/navicard__default.html new file mode 100644 index 000000000..c13af968e --- /dev/null +++ b/blocks/src/test/fixtures/navicard__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/new-list__default.html b/blocks/src/test/fixtures/new-list__default.html new file mode 100644 index 000000000..1fce9dc64 --- /dev/null +++ b/blocks/src/test/fixtures/new-list__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/popular-list__default.html b/blocks/src/test/fixtures/popular-list__default.html new file mode 100644 index 000000000..c51787dea --- /dev/null +++ b/blocks/src/test/fixtures/popular-list__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/profile__default.html b/blocks/src/test/fixtures/profile__default.html new file mode 100644 index 000000000..baf65f213 --- /dev/null +++ b/blocks/src/test/fixtures/profile__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/radar__default.html b/blocks/src/test/fixtures/radar__default.html new file mode 100644 index 000000000..1be853c01 --- /dev/null +++ b/blocks/src/test/fixtures/radar__default.html @@ -0,0 +1,127 @@ + +
+ diff --git a/blocks/src/test/fixtures/rakuten-product-link__default.html b/blocks/src/test/fixtures/rakuten-product-link__default.html new file mode 100644 index 000000000..4ec206446 --- /dev/null +++ b/blocks/src/test/fixtures/rakuten-product-link__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/ranking__default.html b/blocks/src/test/fixtures/ranking__default.html new file mode 100644 index 000000000..33c082316 --- /dev/null +++ b/blocks/src/test/fixtures/ranking__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/search-box__default.html b/blocks/src/test/fixtures/search-box__default.html new file mode 100644 index 000000000..1580e0a5d --- /dev/null +++ b/blocks/src/test/fixtures/search-box__default.html @@ -0,0 +1,3 @@ + + + diff --git a/blocks/src/test/fixtures/sticky-box__default.html b/blocks/src/test/fixtures/sticky-box__default.html new file mode 100644 index 000000000..8b3a29fcf --- /dev/null +++ b/blocks/src/test/fixtures/sticky-box__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/tab-box__default.html b/blocks/src/test/fixtures/tab-box__default.html new file mode 100644 index 000000000..699019229 --- /dev/null +++ b/blocks/src/test/fixtures/tab-box__default.html @@ -0,0 +1,5 @@ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/tab-caption-box__default.html b/blocks/src/test/fixtures/tab-caption-box__default.html new file mode 100644 index 000000000..affb1a787 --- /dev/null +++ b/blocks/src/test/fixtures/tab-caption-box__default.html @@ -0,0 +1,5 @@ + +
Lorem ipsum dolor
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

+
+ diff --git a/blocks/src/test/fixtures/tab-item__default.html b/blocks/src/test/fixtures/tab-item__default.html new file mode 100644 index 000000000..1abcb7442 --- /dev/null +++ b/blocks/src/test/fixtures/tab-item__default.html @@ -0,0 +1,3 @@ + +
+ diff --git a/blocks/src/test/fixtures/tab__default.html b/blocks/src/test/fixtures/tab__default.html new file mode 100644 index 000000000..effc7f120 --- /dev/null +++ b/blocks/src/test/fixtures/tab__default.html @@ -0,0 +1,3 @@ + +
  • Tab 1
  • Tab 2
+ diff --git a/blocks/src/test/fixtures/template__default.html b/blocks/src/test/fixtures/template__default.html new file mode 100644 index 000000000..ba7dc0e88 --- /dev/null +++ b/blocks/src/test/fixtures/template__default.html @@ -0,0 +1 @@ + diff --git a/blocks/src/test/fixtures/timeline-item__default.html b/blocks/src/test/fixtures/timeline-item__default.html new file mode 100644 index 000000000..a93c62153 --- /dev/null +++ b/blocks/src/test/fixtures/timeline-item__default.html @@ -0,0 +1,3 @@ + +
  • ラベル
    タイトル
  • + diff --git a/blocks/src/test/fixtures/timeline__default.html b/blocks/src/test/fixtures/timeline__default.html new file mode 100644 index 000000000..6bb05e527 --- /dev/null +++ b/blocks/src/test/fixtures/timeline__default.html @@ -0,0 +1,19 @@ + +
    Title
      +
    • STEP 1
      Volutpat consequat mauris nunc congue
      +

      Adipiscing enim eu turpis egestas pretium aenean pharetra magna ac.

      +
    • + + + +
    • STEP 2
      Elementum curabitur vitae nunc sed
      +

      Suspendisse in est ante in nibh mauris cursus mattis molestie.

      +
    • + + + +
    • STEP 3
      Adipiscing bibendum est ultricies integer
      +

      Donec ac odio tempor orci dapibus ultrices in iaculis nunc.

      +
    • +
    + diff --git a/blocks/src/test/fixtures/toggle-box__default.html b/blocks/src/test/fixtures/toggle-box__default.html new file mode 100644 index 000000000..430c83846 --- /dev/null +++ b/blocks/src/test/fixtures/toggle-box__default.html @@ -0,0 +1,5 @@ + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius facilis in molestiae quod! Aut doloribus est illum iure porro sunt. Eius illo iusto maxime nihil possimus quae tempora voluptas voluptates.

    +
    + diff --git a/docker/docker-compose.wp7.0-php8.4.yml b/docker/docker-compose.wp7.0-php8.4.yml new file mode 100644 index 000000000..bd649a5f2 --- /dev/null +++ b/docker/docker-compose.wp7.0-php8.4.yml @@ -0,0 +1,111 @@ +services: + # 権限修正用初期化コンテナ + init-permissions-70-php84: + image: wordpress:7.0-php8.4-apache + user: "root" + command: | + sh -c " + chown -R www-data:www-data /var/www/html/wp-content/uploads && + chmod -R 755 /var/www/html/wp-content/uploads && + mkdir -p /var/www/html/wp-content/uploads/cocoon-resources/css-cache && + chown -R www-data:www-data /var/www/html/wp-content/uploads/cocoon-resources && + chmod -R 755 /var/www/html/wp-content/uploads/cocoon-resources + " + volumes: + - uploads_70_php84:/var/www/html/wp-content/uploads + networks: + cocoon-70-php84-network: + ipv4_address: 192.168.110.5 + + # WordPress 7.0 + PHP 8.4 サービス + wordpress-7-0-php84: + image: wordpress:7.0-php8.4-apache + container_name: cocoon-wordpress-7-0-php84 + restart: unless-stopped + user: "33:33" # www-data user + ports: + - "8090:80" + environment: + WORDPRESS_DB_HOST: mysql-7-0-php84:3306 + WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME:-wordpress_70_php84} + WORDPRESS_DB_USER: ${WORDPRESS_DB_USER:-wordpress} + WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD:-wordpress} + WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX:-wp_} + WORDPRESS_DEBUG: ${WORDPRESS_DEBUG:-true} + # Apache設定 + APACHE_RUN_USER: www-data + APACHE_RUN_GROUP: www-data + volumes: + # Cocoonテーマを WordPress テーマディレクトリにマウント + - ..:/var/www/html/wp-content/themes/cocoon + # WordPress データの永続化 + - wordpress_70_php84_data:/var/www/html + # アップロードディレクトリ(書き込み権限が必要) + - uploads_70_php84:/var/www/html/wp-content/uploads + depends_on: + mysql-7-0-php84: + condition: service_healthy + init-permissions-70-php84: + condition: service_completed_successfully + networks: + cocoon-70-php84-network: + ipv4_address: 192.168.110.10 + + # MySQL データベース (WordPress 7.0 + PHP 8.4用) + mysql-7-0-php84: + image: mysql:8.0 + container_name: cocoon-mysql-7-0-php84 + restart: unless-stopped + environment: + MYSQL_DATABASE: ${WORDPRESS_DB_NAME:-wordpress_70_php84} + MYSQL_USER: ${WORDPRESS_DB_USER:-wordpress} + MYSQL_PASSWORD: ${WORDPRESS_DB_PASSWORD:-wordpress} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + volumes: + - mysql_70_php84_data:/var/lib/mysql + # 本番用DBに加えて統合テスト用DB(wordpress_test)を初期化時に作成 + - ./mysql-init-wp7.0-php8.4:/docker-entrypoint-initdb.d + ports: + - "3090:3306" + networks: + cocoon-70-php84-network: + ipv4_address: 192.168.110.20 + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "wordpress", "--password=wordpress"] + timeout: 20s + retries: 10 + + # phpMyAdmin (WordPress 7.0 + PHP 8.4用) + phpmyadmin-7-0-php84: + image: phpmyadmin/phpmyadmin:latest + container_name: cocoon-phpmyadmin-7-0-php84 + restart: unless-stopped + ports: + - "8190:80" + environment: + PMA_HOST: mysql-7-0-php84 + PMA_PORT: 3306 + PMA_USER: ${WORDPRESS_DB_USER:-wordpress} + PMA_PASSWORD: ${WORDPRESS_DB_PASSWORD:-wordpress} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + depends_on: + - mysql-7-0-php84 + networks: + cocoon-70-php84-network: + ipv4_address: 192.168.110.30 + +volumes: + wordpress_70_php84_data: + driver: local + mysql_70_php84_data: + driver: local + uploads_70_php84: + driver: local + +networks: + cocoon-70-php84-network: + driver: bridge + ipam: + config: + - subnet: 192.168.110.0/24 + gateway: 192.168.110.1 diff --git a/docker/mysql-init-wp7.0-php8.4/01-create-test-db.sql b/docker/mysql-init-wp7.0-php8.4/01-create-test-db.sql new file mode 100644 index 000000000..151ce71e4 --- /dev/null +++ b/docker/mysql-init-wp7.0-php8.4/01-create-test-db.sql @@ -0,0 +1,5 @@ +-- WordPress 統合テスト用データベースの作成 +-- (WP テストスイートは指定 DB の全テーブルを破棄するため、本番用とは別の専用 DB を用意する) +CREATE DATABASE IF NOT EXISTS `wordpress_test` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +GRANT ALL PRIVILEGES ON `wordpress_test`.* TO 'wordpress'@'%'; +FLUSH PRIVILEGES; diff --git a/phpunit-integration.xml.dist b/phpunit-integration.xml.dist new file mode 100644 index 000000000..ef5d8c462 --- /dev/null +++ b/phpunit-integration.xml.dist @@ -0,0 +1,33 @@ + + + + + + + tests/Integration + + + + diff --git a/tests/Integration/NewListBlockRenderTest.php b/tests/Integration/NewListBlockRenderTest.php new file mode 100644 index 000000000..9c2a49a6d --- /dev/null +++ b/tests/Integration/NewListBlockRenderTest.php @@ -0,0 +1,180 @@ +category->create([ + 'name' => 'ゴールデンカテゴリー', + 'slug' => 'golden-category', + ]); + + // 決定的な投稿を seed(固定タイトル・固定日付・固定slug・固定抜粋)。 + // 実DBの既存投稿に依存しないよう、必要な投稿だけをこのテスト内で生成する。 + // 日付降順(order=desc)で新しい順に並ぶ: C > B > A。 + $seeds = [ + [ + 'post_title' => 'ゴールデンマスター記事A', + 'post_name' => 'golden-post-a', + 'post_date' => '2021-01-01 09:00:00', + 'post_content' => 'これは記事Aの本文です。', + 'post_excerpt' => '記事Aの抜粋テキストです。', + ], + [ + 'post_title' => 'ゴールデンマスター記事B', + 'post_name' => 'golden-post-b', + 'post_date' => '2021-02-02 09:00:00', + 'post_content' => 'これは記事Bの本文です。', + 'post_excerpt' => '記事Bの抜粋テキストです。', + ], + [ + 'post_title' => 'ゴールデンマスター記事C', + 'post_name' => 'golden-post-c', + 'post_date' => '2021-03-03 09:00:00', + 'post_content' => 'これは記事Cの本文です。', + 'post_excerpt' => '記事Cの抜粋テキストです。', + ], + ]; + + foreach ($seeds as $seed) { + $post_id = self::factory()->post->create(array_merge($seed, [ + 'post_status' => 'publish', + 'post_type' => 'post', + 'post_category' => [$cat_id], + ])); + $this->post_ids[] = $post_id; + } + } + + /** + * 既定属性(count=3, 日付/抜粋なし)の新着リストが golden と一致すること。 + */ + public function test_新着リストブロックの既定レンダリングが_golden_と一致する(): void + { + $attrs = [ + 'count' => 3, + 'sticky' => false, + 'showAllCats' => true, + ]; + $this->assertRenderMatchesGolden('new-list-default', $attrs); + } + + /** + * 日付・抜粋を有効化した新着リストが golden と一致すること。 + */ + public function test_新着リストブロックの日付抜粋つきレンダリングが_golden_と一致する(): void + { + $attrs = [ + 'count' => 3, + 'sticky' => false, + 'showAllCats' => true, + 'date' => true, + 'snippet' => true, + ]; + $this->assertRenderMatchesGolden('new-list-date-snippet', $attrs); + } + + /** + * 指定属性でブロックをレンダリングし、正規化後 HTML を golden と比較する。 + * UPDATE_GOLDEN=1 のときは golden を書き出す。 + */ + private function assertRenderMatchesGolden(string $name, array $attrs): void + { + $rendered = $this->render_new_list_block($attrs); + $actual = $this->normalize($rendered); + $golden_file = self::FIXTURES_DIR . '/' . $name . '.html'; + + if (getenv('UPDATE_GOLDEN')) { + if (!is_dir(self::FIXTURES_DIR)) { + mkdir(self::FIXTURES_DIR, 0777, true); + } + file_put_contents($golden_file, $actual); + // 生成モードでは書き出しの成否のみ確認する。 + $this->assertFileExists($golden_file); + return; + } + + $this->assertFileExists( + $golden_file, + "golden ファイルがありません: {$golden_file}\n" . + 'UPDATE_GOLDEN=1 を付けて実行すると生成できます。' + ); + $expected = file_get_contents($golden_file); + $this->assertSame($expected, $actual, "レンダリング結果が golden ({$name}) と一致しません。"); + } + + /** + * new-list ブロックを do_blocks() でサーバーサイドレンダリングする。 + */ + private function render_new_list_block(array $attrs): string + { + $json = wp_json_encode($attrs); + $block = ''; + return do_blocks($block); + } + + /** + * 揮発要素を正規化する。 + * + * 正規化ルール: + * - 行末の空白を除去(末尾空白の揺れを吸収) + * - パーマリンク等の投稿ID/タームID (?p=123 / ?cat=1 等) → 0 + * - get_post_class の post-123 クラス → post-0 + * - entry-date 内の日付テキスト (get_the_time 出力) → __DATE__ + * - アセットの ?ver=... キャッシュバスター → ?ver=__VER__ + * + * 固定 seed(固定日付・固定抜粋・固定カテゴリー slug)により、 + * タイトル・抜粋・カテゴリークラスは正規化不要で安定する。 + */ + private function normalize(string $html): string + { + // 行末の空白を整理 + $html = preg_replace('/[ \t]+(\r?\n)/', '$1', $html); + + // パーマリンク中の投稿ID/タームID + $html = preg_replace('/([?&](?:p|page_id|post|cat|tag_id|m)=)\d+/', '${1}0', $html); + + // get_post_class の数値付き投稿クラス + // (post-date/post-update 等は数字を含まないため影響しない) + $html = preg_replace('/\bpost-\d+\b/', 'post-0', $html); + + // entry-date の中身(get_the_time / get_update_time の出力) + $html = preg_replace( + '#()#s', + '${1}__DATE__${2}', + $html + ); + + // アセット URL のバージョンクエリ + $html = preg_replace('/([?&]ver=)[^"\'&\s]+/', '${1}__VER__', $html); + + return $html; + } +} diff --git a/tests/Integration/bootstrap.php b/tests/Integration/bootstrap.php index 284462ccf..18fa9ecf7 100644 --- a/tests/Integration/bootstrap.php +++ b/tests/Integration/bootstrap.php @@ -15,7 +15,7 @@ * vendor/bin/phpunit --testsuite integration * * ローカル環境でのセットアップ: - * - Docker を使用する場合は docker/docker-compose.test.yml を参照 + * - Docker を使用する場合は docker/docker-compose.wp7.0-php8.4.yml を参照 * - GitHub Actions では自動的にセットアップされます */ @@ -43,13 +43,39 @@ echo "WP_TESTS_DIR 環境変数を設定するか、WordPress テストスイートをインストールしてください。\n\n"; echo "セットアップ方法:\n"; echo " 1. GitHub Actions: .github/workflows/phpunit.yml に設定済み\n"; - echo " 2. ローカル: bin/install-wp-tests.sh を使用\n"; + echo " 2. ローカル: docker/docker-compose.wp7.0-php8.4.yml を使用\n"; echo " 3. 手動: export WP_TESTS_DIR=/path/to/wordpress-develop/tests/phpunit\n"; exit(1); } -// Composer オートローダー -require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; +// Composer オートローダー(統合レーン専用ツールチェーン: PHPUnit 9.6 + polyfills) +// +// ユニットレーンのテーマ vendor は PHPUnit 11 だが、WordPress 7.0 テストスイートは +// PHPUnit 10 で削除された parseTestMethodAnnotations() を使うため 9.x が必須。 +// ここでテーマ vendor(PHPUnit 11)を読み込むと 9.6 実行系とクラスが混在して fatal に +// なるため、統合レーンは tests/integration-tooling/vendor(PHPUnit 9.6 + polyfills)を用いる。 +$_tooling_autoload = __DIR__ . '/../integration-tooling/vendor/autoload.php'; +if (file_exists($_tooling_autoload)) { + require_once $_tooling_autoload; +} + +// テスト用クラス(Cocoon\Tests\ => tests/)の PSR-4 オートローダー。 +// テーマ本体の関数は WordPress がテーマ切り替え時に読み込むため、ここでは扱わない。 +spl_autoload_register(function ($class) { + $prefix = 'Cocoon\\Tests\\'; + if (strncmp($class, $prefix, strlen($prefix)) !== 0) { + return; + } + $rel = substr($class, strlen($prefix)); + $file = dirname(__DIR__) . '/' . str_replace('\\', '/', $rel) . '.php'; + if (is_file($file)) { + require $file; + } +}); + +// WordPress テストスイートの関数群を読み込み(tests_add_filter 等を定義) +// これを先に読み込まないと、下の tests_add_filter() が未定義になる。 +require_once $_tests_dir . '/includes/functions.php'; // テーマ読み込み関数を WordPress のテストブートストラップ前に登録 $_theme_dir = dirname(__DIR__, 2); diff --git a/tests/Integration/fixtures/new-list-date-snippet.html b/tests/Integration/fixtures/new-list-date-snippet.html new file mode 100644 index 000000000..a02f84ff7 --- /dev/null +++ b/tests/Integration/fixtures/new-list-date-snippet.html @@ -0,0 +1,51 @@ + \ No newline at end of file diff --git a/tests/Integration/fixtures/new-list-default.html b/tests/Integration/fixtures/new-list-default.html new file mode 100644 index 000000000..6d489620f --- /dev/null +++ b/tests/Integration/fixtures/new-list-default.html @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/tests/integration-tooling/composer.json b/tests/integration-tooling/composer.json new file mode 100644 index 000000000..467ae7037 --- /dev/null +++ b/tests/integration-tooling/composer.json @@ -0,0 +1,12 @@ +{ + "name": "cocoon/integration-tooling", + "description": "Isolated toolchain for the WordPress integration test lane. Kept separate from the theme's root composer.json (PHPUnit ^11) because the WordPress 7.0 test suite calls PHPUnit\\Util\\Test::parseTestMethodAnnotations(), which was removed in PHPUnit 10. The integration lane therefore requires PHPUnit 9.x + yoast/phpunit-polyfills ^1.1. Install with 'composer install' inside this directory; the resulting vendor/bin/phpunit is the integration runner.", + "license": "GPL-2.0-or-later", + "require-dev": { + "phpunit/phpunit": "^9.6", + "yoast/phpunit-polyfills": "^1.1" + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/tests/integration-tooling/composer.lock b/tests/integration-tooling/composer.lock new file mode 100644 index 000000000..94d227d38 --- /dev/null +++ b/tests/integration-tooling/composer.lock @@ -0,0 +1,1862 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ad8e1c9397278d5042595fc9e25e454d", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.35", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b", + "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.10", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:48:07+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:22:56+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:03:27+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:57:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/41aaac462fbd80feb8dd129e489f4bbc53fe26b0", + "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2025-08-10T04:54:36+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +}