diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a6f1eac --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Exclude from release archives +/.github export-ignore +/Tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +grumphp.yml export-ignore +Makefile export-ignore +phpstan.neon export-ignore +phpstan-baseline.neon export-ignore +phpunit.xml export-ignore +rector.php export-ignore diff --git a/.github/workflows/tasks.yml b/.github/workflows/tasks.yml index ac43d24..a9c7b7e 100644 --- a/.github/workflows/tasks.yml +++ b/.github/workflows/tasks.yml @@ -9,25 +9,21 @@ jobs: strategy: fail-fast: false matrix: - php: [ '8.1', '8.2', '8.3', '8.4' ] - typo3: [ '11', '12', '13' ] - exclude: - - php: '8.1' - typo3: '13' - - php: '8.4' - typo3: '11' + php: [ '82', '83', '84' ] + typo3: [ '12', '13' ] + container: + image: ghcr.io/typo3/core-testing-php${{ matrix.php }}:latest steps: - - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} + - name: Install dependencies + run: apk add --no-cache nodejs - uses: actions/checkout@v5 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.composer/cache/files key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-${{ matrix.php }}-composer- - - run: composer require typo3/minimal="^${{ matrix.typo3 }}" --dev - - run: composer install --no-interaction --no-progress - - run: ./vendor/bin/grumphp run --ansi + - run: git config --global --add safe.directory $GITHUB_WORKSPACE + - run: composer switchto${{ matrix.typo3 }} + - run: ./vendor/bin/grumphp run --ansi --no-interaction + - run: composer test diff --git a/.gitignore b/.gitignore index 0c7c416..b1220e8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ vendor public composer.lock var/ +.phpunit.result.cache diff --git a/Classes/EventListener/RenderedEventListener.php b/Classes/EventListener/RenderedEventListener.php index a8a368b..7478d5e 100644 --- a/Classes/EventListener/RenderedEventListener.php +++ b/Classes/EventListener/RenderedEventListener.php @@ -1,5 +1,7 @@ &1 | tee /tmp/act-output.log; \ + echo ""; \ + echo "=== 🏁 Summary ==="; \ + grep "🏁" /tmp/act-output.log || true + + +clean: + docker rm -f $$(docker ps -aq --filter "name=act-") 2>/dev/null || true + diff --git a/Tests/Unit/Service/MinifyServiceTest.php b/Tests/Unit/Service/MinifyServiceTest.php new file mode 100644 index 0000000..dc74aff --- /dev/null +++ b/Tests/Unit/Service/MinifyServiceTest.php @@ -0,0 +1,141 @@ +subject = new MinifyService(); + } + + // ------------------------------------------------------------------------- + // All features off (default) + // ------------------------------------------------------------------------- + + #[Test] + public function minifyKeepsHtmlCommentsWhenNoFeaturesAreEnabled(): void + { + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringContainsString('', $result); + } + + #[Test] + public function minifyKeepsTypo3CommentWhenNoFeaturesAreEnabled(): void + { + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringContainsString('', $result); + } + + // ------------------------------------------------------------------------- + // remove_comments alone — comment removal needs the DOM parser too, + // so the output stays unchanged when only remove_comments is set. + // ------------------------------------------------------------------------- + + #[Test] + public function minifyDoesNotRemoveCommentsWithOnlyRemoveCommentsFlagEnabled(): void + { + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_comments'] = '1'; + + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + // Without the DOM parser the minifier cannot parse the tree, + // so the comment is left in the output. + self::assertStringContainsString('', $result); + } + + // ------------------------------------------------------------------------- + // remove_comments + optimize_via_html_dom_parser (the realistic combination) + // ------------------------------------------------------------------------- + + #[Test] + public function minifyRemovesRegularCommentsWhenRemoveCommentsAndDomParserAreEnabled(): void + { + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_comments'] = '1'; + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['optimize_via_html_dom_parser'] = '1'; + + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringNotContainsString('', $result); + self::assertStringContainsString('

Hello

', $result); + } + + #[Test] + public function minifyPreservesTypo3CommentWhenRemoveCommentsAndDomParserAreEnabled(): void + { + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_comments'] = '1'; + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['optimize_via_html_dom_parser'] = '1'; + + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringContainsString('', $result); + } + + #[Test] + public function minifyRemovesRegularCommentButKeepsTypo3CommentWhenBothFlagsAreEnabled(): void + { + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_comments'] = '1'; + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['optimize_via_html_dom_parser'] = '1'; + + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringContainsString('', $result); + self::assertStringNotContainsString('', $result); + } + + // ------------------------------------------------------------------------- + // Output integrity + // ------------------------------------------------------------------------- + + #[Test] + public function minifyReturnsFallbackHtmlWhenMinifierProducesEmptyString(): void + { + // An empty string causes the minifier to return '' which triggers + // the $originalHtml fallback — the method must not crash. + $result = $this->subject->minify(''); + + self::assertSame('', $result); + } + + #[Test] + public function minifyAppendsClosingBodyTagWhenAbsentFromMinifiedOutput(): void + { + // Enable features that actually cause HtmlMin to strip closing tags. + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_comments'] = '1'; + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['optimize_via_html_dom_parser'] = '1'; + $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['minify']['remove_omitted_html_tags'] = '1'; + + $html = '

Hello

'; + + $result = $this->subject->minify($html); + + self::assertStringEndsWith('', $result); + } +} diff --git a/composer.json b/composer.json index 2072b57..ebaafdb 100644 --- a/composer.json +++ b/composer.json @@ -8,22 +8,28 @@ "source": "https://github.com/pluswerk/minify" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "composer-runtime-api": "^2", - "typo3/cms-core": "^11.5 || ^12.4 || ^13.4", - "voku/html-min": "^4.5.1" + "typo3/cms-core": "^12.4 || ^13.4", + "voku/html-min": "^5.0.0" }, "require-dev": { "andersundsehr/ssi-include": "^1.1.0 || ^2.2.0", "pluswerk/grumphp-config": "^7 || ^10", - "saschaegerer/phpstan-typo3": "^1.10.0 || ^2.1.1", - "ssch/typo3-rector": "^2.5.0 || ^3.6.2" + "saschaegerer/phpstan-typo3": "^1.10.2 || ^2.1.1", + "ssch/typo3-rector": "^2.15.2 || ^3.14.1", + "typo3/testing-framework": "^8.2 || ^9.4" }, "autoload": { "psr-4": { "Pluswerk\\PlusMinify\\": "Classes/" } }, + "autoload-dev": { + "psr-4": { + "Pluswerk\\PlusMinify\\Tests\\": "Tests/" + } + }, "config": { "allow-plugins": { "ergebnis/composer-normalize": true, @@ -39,5 +45,16 @@ "typo3/cms": { "extension-key": "minify" } + }, + "scripts": { + "switchto12": [ + "rm -rf composer.lock vendor/composer/installed.json", + "@composer req typo3/minimal:^12 --dev --no-interaction --no-progress" + ], + "switchto13": [ + "rm -rf composer.lock vendor/composer/installed.json", + "@composer req typo3/minimal:^13 --dev --no-interaction --no-progress" + ], + "test": "@php ./vendor/bin/phpunit --testdox --stderr" } } diff --git a/ext_emconf.php b/ext_emconf.php deleted file mode 100644 index 5ff53a8..0000000 --- a/ext_emconf.php +++ /dev/null @@ -1,21 +0,0 @@ - '+Pluswerk: Minify', - 'description' => 'Minifies your html at a glace', - 'category' => 'service', - 'author' => 'Stefan Lamm', - 'author_email' => 'stefan.lamm@pluswerk.ag', - 'state' => 'stable', - 'version' => InstalledVersions::getPrettyVersion('pluswerk/minify'), - 'constraints' => [ - 'depends' => [ - 'typo3' => '11.5.0 - 13.99.99', - ], - 'conflicts' => [], - 'suggests' => [], - ], -]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..2f2b108 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + + Tests + + + + + Classes + + +