diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b2278e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# The IDE is assumed to use PSR-2 for PHP files. + +root = true + +[*] +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{yml,yaml}] +indent_size = 2 +tab_width = 2 diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml new file mode 100644 index 0000000..06a6771 --- /dev/null +++ b/.github/workflows/unittests.yml @@ -0,0 +1,61 @@ +name: unittests + +on: [ push, pull_request ] + +jobs: + unittests: + name: '[PHP ${{ matrix.php-version }} | Flow ${{ matrix.flow-version }}] Unit Tests' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: [ 7.4, 8.2, 8.3 ] + flow-version: [ 7.3, 8.3 ] + exclude: + # Disable Flow 8 on PHP 7, as 8.0 is required + - php-version: 7.4 + flow-version: 8.2 + # Disable Flow 8 on PHP 7, as 8.0 is required + - php-version: 7.4 + flow-version: 8.3 + + env: + APP_ENV: true + FLOW_CONTEXT: Testing/Unit + FLOW_DIST_FOLDER: flow-base-distribution + + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, xml, json, zlib, iconv, intl, pdo_sqlite + ini-values: opcache.fast_shutdown=0 + + - name: "Create composer project - No install" + run: composer create-project neos/flow-base-distribution ${{ env.FLOW_DIST_FOLDER }} --prefer-dist --no-progress --no-install "^${{ matrix.flow-version }}" + + - name: "Allow neos composer plugin" + run: composer config --no-plugins allow-plugins.neos/composer-plugin true + working-directory: ${{ env.FLOW_DIST_FOLDER }} + + - name: "Create composer project - Require behat in compatible version" + run: composer require --dev --no-update "neos/behat:@dev" + working-directory: ${{ env.FLOW_DIST_FOLDER }} + + - name: "Create composer project - Install project" + run: composer install + working-directory: ${{ env.FLOW_DIST_FOLDER }} + + - name: Checkout code + uses: actions/checkout@v2 + with: + path: ${{ env.FLOW_DIST_FOLDER }}/DistributionPackages/Netlogix.Eel.JavaScript + + - name: Install netlogix/eel-javascript + run: composer require netlogix/eel-javascript:@dev + working-directory: ${{ env.FLOW_DIST_FOLDER }} + + - name: Run tests + run: bin/phpunit -c DistributionPackages/Netlogix.Eel.JavaScript/Tests/phpunit.xml.dist --testsuite="Unit" --bootstrap "Build/BuildEssentials/PhpUnit/UnitTestBootstrap.php" + working-directory: ${{ env.FLOW_DIST_FOLDER }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..976cdcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea/ +vendor/ +Packages/ + +composer.lock + +Build/ + +Tests/.phpunit.result.cache diff --git a/Classes/Helper/JavaScriptHelper.php b/Classes/Helper/JavaScriptHelper.php new file mode 100644 index 0000000..09c4dea --- /dev/null +++ b/Classes/Helper/JavaScriptHelper.php @@ -0,0 +1,72 @@ +embedWithVariables($path, []); + } + + public function embedWithVariables(string $path, array $variables): string + { + $script = self::minify($path); + if (count($variables) === 0) { + return $script; + } + + $declaredVariables = array_reduce(array_keys($variables), + fn ($vars, $key) => $vars . PHP_EOL . sprintf('var %s = %s;', $key, + json_encode($variables[$key], JSON_PRETTY_PRINT)), ''); + + return trim($declaredVariables . PHP_EOL . $script); + } + + private static function minify(string ...$paths): string + { + $minifier = new JSMinify(array_map([self::class, 'resolvePath'], $paths)); + + return $minifier->minify(); + } + + /** + * Resolve resource:// to absolute uris as the minifier does not "uri like" paths + */ + private static function resolvePath(string $path): string + { + if (strpos($path, 'resource://') !== 0) { + return $path; + } + + $streamWrapper = Bootstrap::$staticObjectManager->get(ResourceStreamWrapper::class); + $reflectionClass = new ReflectionClass($streamWrapper); + + $method = $reflectionClass->getMethod('evaluateResourcePath'); + $method->setAccessible(true); + + return $method->invoke($streamWrapper, $path); + } + + public function allowsCallOfMethod($methodName): bool + { + return true; + } +} diff --git a/Configuration/Settings.Fusion.yaml b/Configuration/Settings.Fusion.yaml new file mode 100644 index 0000000..ce58ddc --- /dev/null +++ b/Configuration/Settings.Fusion.yaml @@ -0,0 +1,4 @@ +Neos: + Fusion: + defaultContext: + 'Netlogix.JavaScript': 'Netlogix\Eel\JavaScript\Helper\JavaScriptHelper' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5559295 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 netlogix GmbH & Co. KG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Tests/Fixtures/bubblesort.js b/Tests/Fixtures/bubblesort.js new file mode 100644 index 0000000..0919f3c --- /dev/null +++ b/Tests/Fixtures/bubblesort.js @@ -0,0 +1,30 @@ +// Bubble sort Implementation using JavaScript +// https://www.geeksforgeeks.org/bubble-sort-algorithms-by-using-javascript/ + +// Creating the bblSort function +function bblSort(arr) { + + for (var i = 0; i < arr.length; i++) { + + // Last i elements are already in place + for (var j = 0; j < (arr.length - i - 1); j++) { + + // Checking if the item at present iteration + // is greater than the next iteration + if (arr[j] > arr[j + 1]) { + + // If the condition is true + // then swap them + var temp = arr[j] + arr[j] = arr[j + 1] + arr[j + 1] = temp + } + } + } + + // Print the sorted array + console.log(arr); +} + +// Now pass this array to the bblSort() function +bblSort(arr); diff --git a/Tests/Fixtures/hello-world.js b/Tests/Fixtures/hello-world.js new file mode 100644 index 0000000..3451e9b --- /dev/null +++ b/Tests/Fixtures/hello-world.js @@ -0,0 +1 @@ +console.log('Hello World'); diff --git a/Tests/Unit/Helper/JavaScriptHelperTest.php b/Tests/Unit/Helper/JavaScriptHelperTest.php new file mode 100644 index 0000000..117f2c5 --- /dev/null +++ b/Tests/Unit/Helper/JavaScriptHelperTest.php @@ -0,0 +1,118 @@ +assertEquals( + <<embed($fileName) + ); + } + + /** + * @test + */ + public function testEmbedWithVariables(): void + { + $fileName = __DIR__ . '/../../Fixtures/hello-world.js'; + + $helper = new JavaScriptHelper(); + + $this->assertEquals( + <<embedWithVariables($fileName, ['someGreatVariable' => 'withSomeValue', 'andAnInt' => 42]) + ); + } + + /** + * @test + */ + public function testCodeIsMinified(): void + { + $fileName = __DIR__ . '/../../Fixtures/bubblesort.js'; + + $helper = new JavaScriptHelper(); + + $this->assertEquals( + <<arr[j+1]){var temp=arr[j] +arr[j]=arr[j+1] +arr[j+1]=temp}}} +console.log(arr)} +bblSort(arr) +JS, + $helper->embedWithVariables($fileName, ['arr' => [234, 43, 55, 63, 5, 6, 235, 547]]) + ); + } + + /** + * @test + */ + public function Flow_resource_Uris_are_resolved(): void + { + $fileName = 'resource://Netlogix.Eel.JavaScript/Private/JavaScript/hello-world.js'; + $previousStaticObjectManager = Bootstrap::$staticObjectManager; + try { + $objectManager = $this->getMockBuilder(ObjectManagerInterface::class)->getMock(); + $streamWrapper = new class extends ResourceStreamWrapper { + protected function evaluateResourcePath($requestedPath, $checkForExistence = true) + { + return __DIR__ . '/../../Fixtures/hello-world.js'; + } + }; + + $objectManager + ->expects(self::once()) + ->method('get') + ->with(ResourceStreamWrapper::class) + ->willReturn($streamWrapper); + + Bootstrap::$staticObjectManager = $objectManager; + + $helper = new JavaScriptHelper(); + + $this->assertEquals( + <<embed($fileName) + ); + } finally { + Bootstrap::$staticObjectManager = $previousStaticObjectManager; + } + } +} diff --git a/Tests/phpunit.xml.dist b/Tests/phpunit.xml.dist new file mode 100644 index 0000000..352ad31 --- /dev/null +++ b/Tests/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + ./Unit/ + + + + + + + + + + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2181254 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "netlogix/eel-javascript", + "description": "Neos Eel Helper to embed inline JavaScript", + "type": "neos-package", + "license": "MIT", + "require": { + "neos/eel": "^7.3 || ^8.3", + "neos/flow": "^7.3 || ^8.3", + "neos/fusion": "^7.3 || ^8.3", + "matthiasmullie/minify": "^1.3" + }, + "autoload": { + "psr-4": { + "Netlogix\\Eel\\JavaScript\\": "Classes/" + } + }, + "extra": { + "neos": { + "package-key": "Netlogix.Eel.JavaScript" + } + } +}