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('