mirror of
https://github.com/werkraum-media/watchlist.git
synced 2024-11-23 13:36:09 +01:00
[FEATURE] Add JavaScript functionality (#2)
This commit is contained in:
parent
916b2723d5
commit
d38af239ee
26 changed files with 842 additions and 116 deletions
13
.gitattributes
vendored
Normal file
13
.gitattributes
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
Tests export-ignore
|
||||
patches export-ignore
|
||||
.github export-ignore
|
||||
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
.php-cs-fixer.dist.php export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
codeception.dist.yml export-ignore
|
||||
|
||||
shell.nix export-ignore
|
106
.github/workflows/ci.yaml
vendored
106
.github/workflows/ci.yaml
vendored
|
@ -1,23 +1,20 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
check-composer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Composer
|
||||
uses: shivammathur/setup-php@v2
|
||||
- uses: cachix/install-nix-action@v17
|
||||
with:
|
||||
php-version: 8.1
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
env:
|
||||
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Validate composer.json
|
||||
run: composer validate
|
||||
run: nix-shell --pure --run project-validate-composer
|
||||
|
||||
php-linting:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -31,62 +28,42 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: PHP lint
|
||||
run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
|
||||
|
||||
xml-linting:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-composer]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- uses: cachix/install-nix-action@v17
|
||||
with:
|
||||
php-version: "8.1"
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Install xmllint
|
||||
run: sudo apt-get install libxml2-utils
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- name: PHPUnit configuration file
|
||||
run: xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist
|
||||
|
||||
- name: Fetch schema for xliff
|
||||
run: wget https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=xliff-core-1.2-strict.xsd
|
||||
|
||||
- name: TYPO3 language files
|
||||
run: xmllint --schema xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf')
|
||||
- name: Validate XML
|
||||
run: nix-shell --pure --run project-validate-xml
|
||||
|
||||
coding-guideline:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- php-linting
|
||||
- xml-linting
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- uses: cachix/install-nix-action@v17
|
||||
with:
|
||||
php-version: "8.1"
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- name: Coding Guideline
|
||||
run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||
- name: Check Coding Guideline
|
||||
run: nix-shell --pure --run project-coding-guideline
|
||||
|
||||
code-quality:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- php-linting
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -103,17 +80,16 @@ jobs:
|
|||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install dependencies with expected TYPO3 version
|
||||
run: composer require --no-interaction --prefer-dist --no-progress
|
||||
- name: Install dependencies
|
||||
run: |-
|
||||
composer require --no-interaction --prefer-dist --no-progress
|
||||
./vendor/bin/codecept build
|
||||
|
||||
- name: Code Quality (by PHPStan)
|
||||
run: ./vendor/bin/phpstan analyse
|
||||
|
||||
tests-mysql:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- php-linting
|
||||
- xml-linting
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -137,14 +113,32 @@ jobs:
|
|||
mysql database: 'typo3'
|
||||
mysql root password: 'root'
|
||||
|
||||
- name: Wait for MySQL
|
||||
run: |
|
||||
while ! mysqladmin ping --host=127.0.0.1 --password=root --silent; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer require --no-interaction --prefer-dist --no-progress
|
||||
run: composer install --no-interaction --prefer-dist --no-progress
|
||||
|
||||
- name: PHPUnit Tests
|
||||
run: |-
|
||||
export typo3DatabaseDriver="pdo_mysql"
|
||||
export typo3DatabaseName="typo3"
|
||||
export typo3DatabaseHost="127.0.0.1"
|
||||
export typo3DatabaseUsername="root"
|
||||
export typo3DatabasePassword="root"
|
||||
./vendor/bin/phpunit --testdox
|
||||
env:
|
||||
typo3DatabaseDriver: "pdo_mysql"
|
||||
typo3DatabaseName: "typo3"
|
||||
typo3DatabaseHost: "127.0.0.1"
|
||||
typo3DatabaseUsername: "root"
|
||||
typo3DatabasePassword: "root"
|
||||
run: ./vendor/bin/phpunit --testdox
|
||||
|
||||
tests-acceptance:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: cachix/install-nix-action@v17
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Run Acceptance Tests
|
||||
run: nix-shell --pure --run project-test-acceptance
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
/composer.lock
|
||||
/.php-cs-fixer.cache
|
||||
/.Build/
|
||||
/vendor/
|
||||
/public/
|
||||
/var/
|
||||
|
|
112
Classes/Middleware/CookieSessionMiddleware.php
Normal file
112
Classes/Middleware/CookieSessionMiddleware.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Watchlist\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use WerkraumMedia\Watchlist\Session\CookieSessionService;
|
||||
|
||||
class CookieSessionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private CookieSessionService $cookieSession;
|
||||
|
||||
public function __construct(
|
||||
CookieSessionService $cookieSession
|
||||
) {
|
||||
$this->cookieSession = $cookieSession;
|
||||
}
|
||||
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
$response = $handler->handle($request);
|
||||
|
||||
if ($this->shouldAddCookie($request)) {
|
||||
return $this->addCookie($response, $request);
|
||||
}
|
||||
if ($this->shouldRemoveCookie($request)) {
|
||||
return $this->removeCookie($response, $request);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function shouldAddCookie(ServerRequestInterface $request): bool
|
||||
{
|
||||
return $this->cookieSession->getCookieValue() !== '';
|
||||
}
|
||||
|
||||
private function addCookie(
|
||||
ResponseInterface $response,
|
||||
ServerRequestInterface $request
|
||||
): ResponseInterface {
|
||||
return $response->withAddedHeader(
|
||||
'Set-Cookie',
|
||||
$this->getCookie($request)->__toString()
|
||||
);
|
||||
}
|
||||
|
||||
private function shouldRemoveCookie(ServerRequestInterface $request): bool
|
||||
{
|
||||
$cookieName = $this->cookieSession->getCookieName();
|
||||
|
||||
return $this->cookieSession->getCookieValue() === ''
|
||||
&& isset($request->getCookieParams()[$cookieName])
|
||||
;
|
||||
}
|
||||
|
||||
private function removeCookie(
|
||||
ResponseInterface $response,
|
||||
ServerRequestInterface $request
|
||||
): ResponseInterface {
|
||||
$cookie = $this->getCookie($request)
|
||||
->withExpires(-1);
|
||||
return $response->withAddedHeader('Set-Cookie', $cookie->__toString());
|
||||
}
|
||||
|
||||
private function getCookie(ServerRequestInterface $request): Cookie
|
||||
{
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
if (!$normalizedParams instanceof NormalizedParams) {
|
||||
throw new \Exception('Could not retrieve normalized params from request.', 1664357339);
|
||||
}
|
||||
|
||||
return new Cookie(
|
||||
$this->cookieSession->getCookieName(),
|
||||
$this->cookieSession->getCookieValue(),
|
||||
$GLOBALS['EXEC_TIME'] + 7776000, // 90 days
|
||||
$normalizedParams->getSitePath() . TYPO3_mainDir,
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Cookie::SAMESITE_STRICT
|
||||
);
|
||||
}
|
||||
}
|
|
@ -23,25 +23,33 @@ declare(strict_types=1);
|
|||
|
||||
namespace WerkraumMedia\Watchlist\Session;
|
||||
|
||||
use TYPO3\CMS\Core\Http\ServerRequest;
|
||||
use TYPO3\CMS\Extbase\Property\PropertyMapper;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
use WerkraumMedia\Watchlist\Domain\Model\Item;
|
||||
use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
|
||||
|
||||
class Typo3FrontendSessionService implements SessionServiceInterface
|
||||
class CookieSessionService implements SessionServiceInterface
|
||||
{
|
||||
private PropertyMapper $propertyMapper;
|
||||
|
||||
public function __construct(
|
||||
PropertyMapper $propertyMapper
|
||||
) {
|
||||
private array $watchlists = [];
|
||||
|
||||
// Seems to be a bug leading to different instances if we use constructor.
|
||||
public function injectPropertyMapper(PropertyMapper $propertyMapper): void
|
||||
{
|
||||
$this->propertyMapper = $propertyMapper;
|
||||
}
|
||||
|
||||
public function getWatchlist(string $identifier): ?Watchlist
|
||||
{
|
||||
$items = $this->getTsfe()->fe_user->getSessionData('watchlist-' . $identifier) ?: [];
|
||||
if ($items === [] || is_array($items) === false) {
|
||||
$cookieName = $this->getCookieName();
|
||||
$cookie = $this->getRequest()->getCookieParams()[$cookieName] ?? '';
|
||||
$items = array_filter(explode(
|
||||
',',
|
||||
$this->getRequest()->getCookieParams()['watchlist'] ?? ''
|
||||
));
|
||||
|
||||
if ($items === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -61,13 +69,24 @@ class Typo3FrontendSessionService implements SessionServiceInterface
|
|||
|
||||
public function update(Watchlist $watchlist): void
|
||||
{
|
||||
$this->getTsfe()->fe_user->setAndSaveSessionData(
|
||||
'watchlist-' . $watchlist->getIdentifier(),
|
||||
array_map(fn (Item $item) => $item->getUniqueIdentifier(), $watchlist->getItems())
|
||||
$this->watchlists[$watchlist->getIdentifier()] = array_map(
|
||||
fn (Item $item) => $item->getUniqueIdentifier(),
|
||||
$watchlist->getItems()
|
||||
);
|
||||
}
|
||||
private function getTsfe(): TypoScriptFrontendController
|
||||
|
||||
public function getCookieName(): string
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
return 'watchlist';
|
||||
}
|
||||
|
||||
public function getCookieValue(): string
|
||||
{
|
||||
return implode(',', $this->watchlists['default'] ?? []);
|
||||
}
|
||||
|
||||
private function getRequest(): ServerRequest
|
||||
{
|
||||
return $GLOBALS['TYPO3_REQUEST'];
|
||||
}
|
||||
}
|
36
Configuration/RequestMiddlewares.php
Normal file
36
Configuration/RequestMiddlewares.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
return [
|
||||
'frontend' => [
|
||||
'watchlist-cookiesession' => [
|
||||
'target' => \WerkraumMedia\Watchlist\Middleware\CookieSessionMiddleware::class,
|
||||
'after' => [
|
||||
'typo3/cms-frontend/content-length-headers',
|
||||
],
|
||||
'before' => [
|
||||
'typo3/cms-frontend/output-compression',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
|
@ -11,3 +11,7 @@ services:
|
|||
|
||||
WerkraumMedia\Watchlist\Domain\Items\Page\ItemHandler:
|
||||
tags: ['watchlist.itemHandler']
|
||||
|
||||
WerkraumMedia\Watchlist\Session\CookieSessionService:
|
||||
# Has state and needs to be shared in order to provide this state to middleware
|
||||
shared: true
|
||||
|
|
51
README.rst
51
README.rst
|
@ -14,6 +14,7 @@ Feature set
|
|||
===========
|
||||
|
||||
The extension provides an controller with actions to add and remove items from watchlist.
|
||||
Each item can only exist once within the watchlist.
|
||||
|
||||
There is also an action to show the current watchlist.
|
||||
|
||||
|
@ -21,11 +22,19 @@ The extension ships with support for items of type ``pages``,
|
|||
but this is more a demonstration an used for testing.
|
||||
Projects are way too different and should provide their own items, see "Custom Items".
|
||||
|
||||
The extension also provides a JavaScript which will parse data-Attributes of DOM and
|
||||
attach listener to add elements to the watchlist.
|
||||
|
||||
Custom Items
|
||||
============
|
||||
|
||||
A developer needs to implement an ``ItemHandler`` and an class representing the ``Item``.
|
||||
|
||||
The used identifier has to be unique throughout the system. The first part is the
|
||||
item type which is returned by the ItemHandler to be handled, followed by a minus and
|
||||
the rest of the identifier, leading to: ``<type>-rest-of-identifier``.
|
||||
Commas should not be used within the identifier.
|
||||
|
||||
``ItemHandler``
|
||||
---------------
|
||||
|
||||
|
@ -51,3 +60,45 @@ The Handler needs to be registered via Symfony Tags, e.g. via ``Services.yaml``:
|
|||
--------
|
||||
|
||||
The class needs to implement the ``WerkraumMedia\Watchlist\Domain\Model\Item``.
|
||||
|
||||
JavaScript
|
||||
==========
|
||||
|
||||
The JavaScript respects two ``data-`` attributes:
|
||||
|
||||
``data-watchlist-counter``
|
||||
Defines that this element will hold the current number of items on watch list.
|
||||
The JavaScript will update the content of the element.
|
||||
|
||||
.. code:: html
|
||||
|
||||
<span data-watchlist-counter class="watchlist-badge-counter"></span>
|
||||
|
||||
``data-watchlist-item``
|
||||
Defines that this element represents an actual item.
|
||||
The attribute needs the identifier of the item as value, e.g.: ``data-watchlist-item="page-1"``
|
||||
|
||||
An EventListener will be added listening for click events on that element.
|
||||
Each click will toggle the state of the item on the watch list.
|
||||
|
||||
The JavaScript will add a CSS class to the element, depending on the state of the item.
|
||||
``watchlist-inactive`` will be added in case the item is not on the watchlist.
|
||||
``watchlist-active`` will be added in case the item is on the watchlist.
|
||||
|
||||
A custom Event will be triggered whenever an item is added or removed from the watchlist.
|
||||
The event is triggered on the ``document``.
|
||||
The event provides the identifier of the item.
|
||||
An example listener can look like:
|
||||
|
||||
.. code:: js
|
||||
|
||||
document.addEventListener('WatchlistUpdate', function(event) {
|
||||
console.log(event.detail.watchlistItem);
|
||||
});
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
A concrete example can be found within ``Tests/Fixtures/FrontendRendering.typoscript``.
|
||||
This example includes the provided JavaScript file as well as some custom CSS and Markup.
|
||||
The content element is not necessary.
|
||||
|
|
112
Resources/Public/JavaScript/Watchlist.js
Normal file
112
Resources/Public/JavaScript/Watchlist.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
(function () {
|
||||
|
||||
// From: https://plainjs.com/javascript/utilities/set-cookie-get-cookie-and-delete-cookie-5/
|
||||
const cookie = {
|
||||
name: 'watchlist',
|
||||
days: 7,
|
||||
get: function() {
|
||||
let v = document.cookie.match('(^|;) ?' + cookie.name + '=([^;]*)(;|$)');
|
||||
return v ? v[2] : null;
|
||||
},
|
||||
set: function(value) {
|
||||
let d = new Date;
|
||||
d.setTime(d.getTime() + 24 * 60 * 60 * 1000 * cookie.days);
|
||||
document.cookie = cookie.name + "=" + value + ";path=/;expires=" + d.toGMTString();
|
||||
},
|
||||
delete: function() {
|
||||
let d = new Date;
|
||||
d.setTime(d.getTime() -1);
|
||||
document.cookie = cookie.name + "=;path=/;expires=" + d.toGMTString();
|
||||
}
|
||||
};
|
||||
|
||||
const watchlist = {
|
||||
get: function() {
|
||||
const cookieValue = cookie.get();
|
||||
if (cookieValue === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return cookieValue.split(',');
|
||||
},
|
||||
save: function(items) {
|
||||
var cookieValue = items.join(',');
|
||||
|
||||
if (cookieValue == '') {
|
||||
cookie.delete();
|
||||
} else {
|
||||
cookie.set(items.join(','));
|
||||
}
|
||||
},
|
||||
toggleItem: function(identifier) {
|
||||
let items = watchlist.get();
|
||||
const position = items.indexOf(identifier);
|
||||
|
||||
if (position === -1) {
|
||||
items = items.concat(identifier);
|
||||
} else {
|
||||
items.splice(position, 1);
|
||||
}
|
||||
|
||||
watchlist.save(items);
|
||||
watchlist.triggerUpdateEvent(identifier);
|
||||
},
|
||||
triggerUpdateEvent: function(identifier) {
|
||||
const event = new CustomEvent('WatchlistUpdate', {
|
||||
detail: {
|
||||
watchlistItem: identifier
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
const watchlistHtml = {
|
||||
selectors: {
|
||||
items: '[data-watchlist-item]',
|
||||
counter: '[data-watchlist-counter]'
|
||||
},
|
||||
getItems: function() {
|
||||
return document.querySelectorAll(watchlistHtml.selectors.items);
|
||||
},
|
||||
getCounters: function() {
|
||||
return document.querySelectorAll(watchlistHtml.selectors.counter);
|
||||
},
|
||||
update: function() {
|
||||
watchlistHtml.updateCounter();
|
||||
watchlistHtml.updateItems();
|
||||
},
|
||||
updateCounter: function() {
|
||||
const count = watchlist.get().length;
|
||||
watchlistHtml.getCounters().forEach(function (element) {
|
||||
element.innerText = count;
|
||||
});
|
||||
},
|
||||
updateItems: function() {
|
||||
const items = watchlist.get();
|
||||
watchlistHtml.getItems().forEach(function (element) {
|
||||
if (items.indexOf(element.dataset.watchlistItem) === -1) {
|
||||
element.classList.add('watchlist-inactive');
|
||||
element.classList.remove('watchlist-active');
|
||||
return;
|
||||
}
|
||||
|
||||
element.classList.remove('watchlist-inactive');
|
||||
element.classList.add('watchlist-active');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
watchlistHtml.update();
|
||||
|
||||
watchlistHtml.getItems().forEach(function (element) {
|
||||
element.addEventListener('click', function(event) {
|
||||
watchlist.toggleItem(event.currentTarget.dataset.watchlistItem);
|
||||
});
|
||||
});
|
||||
});
|
||||
document.addEventListener('WatchlistUpdate', function(event) {
|
||||
watchlistHtml.update();
|
||||
});
|
||||
})();
|
0
Tests/Acceptance/Data/.gitkeep
Normal file
0
Tests/Acceptance/Data/.gitkeep
Normal file
95
Tests/Acceptance/JavaScriptCest.php
Normal file
95
Tests/Acceptance/JavaScriptCest.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Watchlist\Tests\Acceptance;
|
||||
|
||||
use WerkraumMedia\Watchlist\Tests\Acceptance\Support\AcceptanceTester;
|
||||
|
||||
class JavaScriptCest
|
||||
{
|
||||
private const SELECTOR_COUNTER = '.watchlist-badge-counter';
|
||||
|
||||
public function counterShowsZeroByDefault(AcceptanceTester $I): void
|
||||
{
|
||||
$I->amOnPage('/');
|
||||
$I->see('Watchlist is empty');
|
||||
$I->see('0', self::SELECTOR_COUNTER);
|
||||
}
|
||||
|
||||
public function canAddItem(AcceptanceTester $I): void
|
||||
{
|
||||
$I->amOnPage('/');
|
||||
$I->see('Watchlist is empty');
|
||||
|
||||
$I->see('Page 1 zur Merkliste hinzufügen');
|
||||
$I->dontSee('Page 1 von Merkliste entfernen');
|
||||
$I->click('button[data-watchlist-item="page-1"]');
|
||||
|
||||
$I->dontSee('Page 1 zur Merkliste hinzufügen');
|
||||
$I->see('Page 1 von Merkliste entfernen');
|
||||
$I->see('1', self::SELECTOR_COUNTER);
|
||||
}
|
||||
|
||||
public function canRemoveItem(AcceptanceTester $I): void
|
||||
{
|
||||
$this->canAddItem($I);
|
||||
|
||||
$I->click('button[data-watchlist-item="page-1"]');
|
||||
|
||||
$I->see('Page 1 zur Merkliste hinzufügen');
|
||||
$I->dontSee('Page 1 von Merkliste entfernen');
|
||||
$I->see('0', self::SELECTOR_COUNTER);
|
||||
}
|
||||
|
||||
public function canAddTwoItems(AcceptanceTester $I): void
|
||||
{
|
||||
$this->canAddItem($I);
|
||||
|
||||
$I->click('button[data-watchlist-item="page-2"]');
|
||||
$I->dontSee('Page 2 zur Merkliste hinzufügen');
|
||||
$I->see('Page 2 von Merkliste entfernen');
|
||||
$I->see('2', self::SELECTOR_COUNTER);
|
||||
}
|
||||
|
||||
public function canRemoveFirstItem(AcceptanceTester $I): void
|
||||
{
|
||||
$this->canAddTwoItems($I);
|
||||
|
||||
$I->click('button[data-watchlist-item="page-1"]');
|
||||
$I->see('Page 1 zur Merkliste hinzufügen');
|
||||
$I->see('Page 2 von Merkliste entfernen');
|
||||
$I->dontSee('Page 2 zur Merkliste hinzufügen');
|
||||
$I->see('1', self::SELECTOR_COUNTER);
|
||||
}
|
||||
|
||||
public function keepsStateOnReload(AcceptanceTester $I): void
|
||||
{
|
||||
$this->canAddItem($I);
|
||||
|
||||
$I->amOnPage('/');
|
||||
|
||||
$I->dontSee('Page 1 zur Merkliste hinzufügen');
|
||||
$I->see('Page 1 von Merkliste entfernen');
|
||||
$I->see('1', self::SELECTOR_COUNTER);
|
||||
}
|
||||
}
|
51
Tests/Acceptance/Support/AcceptanceTester.php
Normal file
51
Tests/Acceptance/Support/AcceptanceTester.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Watchlist\Tests\Acceptance\Support;
|
||||
|
||||
use Codeception\Actor;
|
||||
use WerkraumMedia\Watchlist\Tests\Acceptance\Support\_generated\AcceptanceTesterActions;
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method void pause()
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class AcceptanceTester extends Actor
|
||||
{
|
||||
use AcceptanceTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
61
Tests/Acceptance/Support/Environment.php
Normal file
61
Tests/Acceptance/Support/Environment.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Watchlist\Tests\Acceptance\Support;
|
||||
|
||||
use TYPO3\TestingFramework\Core\Acceptance\Extension\BackendEnvironment;
|
||||
|
||||
/**
|
||||
* Load various core extensions and styleguide and call styleguide generator
|
||||
*/
|
||||
class Environment extends BackendEnvironment
|
||||
{
|
||||
/**
|
||||
* Load a list of core extensions and styleguide
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $localConfig = [
|
||||
'coreExtensionsToLoad' => [
|
||||
'install',
|
||||
'core',
|
||||
'backend',
|
||||
'extbase',
|
||||
'frontend',
|
||||
'fluid',
|
||||
'fluid_styled_content',
|
||||
],
|
||||
'testExtensionsToLoad' => [
|
||||
'typo3conf/ext/watchlist',
|
||||
],
|
||||
'csvDatabaseFixtures' => [
|
||||
__DIR__ . '/../../Fixtures/BasicDatabase.csv',
|
||||
],
|
||||
'additionalFoldersToCreate' => [
|
||||
'config',
|
||||
],
|
||||
'pathsToLinkInTestInstance' => [
|
||||
'typo3conf/ext/watchlist/Tests/Fixtures/Sites/' => 'config/sites',
|
||||
],
|
||||
];
|
||||
}
|
2
Tests/Acceptance/Support/_generated/.gitignore
vendored
Normal file
2
Tests/Acceptance/Support/_generated/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
2
Tests/Acceptance/_output/.gitignore
vendored
Normal file
2
Tests/Acceptance/_output/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
8
Tests/Fixtures/BasicDatabase.csv
Normal file
8
Tests/Fixtures/BasicDatabase.csv
Normal file
|
@ -0,0 +1,8 @@
|
|||
"pages",,,,,,
|
||||
,"uid","pid","slug","title",,
|
||||
,1,0,"/","Page Title",,
|
||||
,2,1,"/page-2","Page 2 Title",,
|
||||
"sys_template",,,,,,
|
||||
,"uid","pid","root","clear","constants","config"
|
||||
,1,1,1,3,"databasePlatform = mysql","<INCLUDE_TYPOSCRIPT: source=""FILE:EXT:watchlist/Tests/Fixtures/FrontendRendering.typoscript"">
|
||||
<INCLUDE_TYPOSCRIPT: source=""FILE:EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript"">"
|
|
35
Tests/Fixtures/FrontendRendering.typoscript
Normal file
35
Tests/Fixtures/FrontendRendering.typoscript
Normal file
|
@ -0,0 +1,35 @@
|
|||
page = PAGE
|
||||
page {
|
||||
# Include JavaScript in order to test the JavaScript
|
||||
includeJSFooter.watchlist = EXT:watchlist/Resources/Public/JavaScript/Watchlist.js
|
||||
|
||||
# Render the content element
|
||||
10 =< tt_content.watchlist_watchlist.20
|
||||
|
||||
# Add minimum CSS for checking functionality within Acceptance tests
|
||||
cssInline {
|
||||
10 = TEXT
|
||||
10.value (
|
||||
.watchlist-inactive .remove-from-list { display: none; }
|
||||
.watchlist-active .add-to-list { display: none; }
|
||||
)
|
||||
}
|
||||
|
||||
# Add some example markup for Acceptance Tests
|
||||
20 = TEXT
|
||||
20.value (
|
||||
<span data-watchlist-counter class="watchlist-badge-counter"></span>
|
||||
|
||||
<button data-watchlist-item="page-1" class="watchlist-btn watchlist-inactive">
|
||||
Page 1
|
||||
<span class="add-to-list">zur Merkliste hinzufügen</span>
|
||||
<span class="remove-from-list">von Merkliste entfernen</span>
|
||||
</button>
|
||||
|
||||
<button data-watchlist-item="page-2" class="watchlist-btn watchlist-inactive">
|
||||
Page 2
|
||||
<span class="add-to-list">zur Merkliste hinzufügen</span>
|
||||
<span class="remove-from-list">von Merkliste entfernen</span>
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -21,8 +21,9 @@ declare(strict_types=1);
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\Watchlist\Tests\Functional\Frontend;
|
||||
namespace WerkraumMedia\Watchlist\Tests\Functional;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalResponse;
|
||||
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
|
||||
|
@ -40,21 +41,15 @@ class BasicsTest extends FunctionalTestCase
|
|||
'typo3conf/ext/watchlist',
|
||||
];
|
||||
|
||||
protected $pathsToProvideInTestInstance = [
|
||||
'typo3conf/ext/watchlist/Tests/Functional/Frontend/Fixtures/Sites/' => 'typo3conf/sites/',
|
||||
protected $pathsToLinkInTestInstance = [
|
||||
'typo3conf/ext/watchlist/Tests/Fixtures/Sites' => 'typo3conf/sites',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpBackendUserFromFixture(1);
|
||||
|
||||
$this->importCSVDataSet(__DIR__ . '/Fixtures/BasicDatabase.csv');
|
||||
$this->setUpFrontendRootPage(1, [
|
||||
'EXT:watchlist/Tests/Functional/Frontend/Fixtures/FrontendRendering.typoscript',
|
||||
'EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript',
|
||||
]);
|
||||
$this->importCSVDataSet(__DIR__ . '/../Fixtures/BasicDatabase.csv');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,14 +78,14 @@ class BasicsTest extends FunctionalTestCase
|
|||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertIsRedirect('http://localhost/?id=1', $result);
|
||||
self::assertHasCookie('fe_typo_user', $result);
|
||||
self::assertCookie('page-1', $this->getCookie($result));
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withHeader('Cookie', self::getCookies($result));
|
||||
$request = $request->withHeader('Cookie', 'watchlist=page-1');
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertStringContainsString('Page Title', $result->getBody()->__toString());
|
||||
self::assertStringContainsString('<li>Page Title</li>', $result->getBody()->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,10 +100,10 @@ class BasicsTest extends FunctionalTestCase
|
|||
$request = $request->withQueryParameter('tx_watchlist_watchlist[item]', 'page-1');
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
$cookies = self::getCookies($result);
|
||||
self::assertCookie('page-1', $this->getCookie($result));
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withHeader('Cookie', $cookies);
|
||||
$request = $request->withHeader('Cookie', 'watchlist=page-1');
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[redirectUri]', $request->getUri()->__toString());
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[action]', 'remove');
|
||||
|
@ -116,9 +111,11 @@ class BasicsTest extends FunctionalTestCase
|
|||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertIsRedirect('http://localhost/?id=1', $result);
|
||||
$cookie = $this->getCookie($result);
|
||||
self::assertInstanceOf(Cookie::class, $cookie);
|
||||
self::assertLessThan(time(), $cookie->getExpiresTime());
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withHeader('Cookie', $cookies);
|
||||
$request = $request->withPageId(1);
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
|
@ -132,13 +129,24 @@ class BasicsTest extends FunctionalTestCase
|
|||
self::assertSame($redirectLocation, $result->getHeader('location')[0] ?? '');
|
||||
}
|
||||
|
||||
private static function assertHasCookie(string $cookieName, InternalResponse $result): void
|
||||
private static function assertCookie(string $value, ?Cookie $cookie): void
|
||||
{
|
||||
self::assertStringContainsString($cookieName . '=', self::getCookies($result));
|
||||
self::assertInstanceOf(Cookie::class, $cookie);
|
||||
self::assertSame('watchlist', $cookie->getName());
|
||||
self::assertSame('page-1', $cookie->getValue());
|
||||
self::assertNull($cookie->getDomain());
|
||||
self::assertSame('/typo3/', $cookie->getPath());
|
||||
self::assertSame('strict', $cookie->getSameSite());
|
||||
self::assertFalse($cookie->isSecure());
|
||||
}
|
||||
|
||||
private static function getCookies(InternalResponse $result): string
|
||||
private function getCookie(InternalResponse $result): ?Cookie
|
||||
{
|
||||
return explode(' ', $result->getHeader('Set-Cookie')[0] ?? '', 2)[0] ?? '';
|
||||
$cookie = $result->getHeader('Set-Cookie')[0] ?? '';
|
||||
if ($cookie === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Cookie::fromString($cookie);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pages
|
||||
,uid,pid,slug,title
|
||||
,1,0,/,Page Title
|
|
|
@ -1,2 +0,0 @@
|
|||
page = PAGE
|
||||
page.10 =< tt_content.watchlist_watchlist.20
|
33
codeception.dist.yml
Normal file
33
codeception.dist.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
namespace: WerkraumMedia\Watchlist\Tests\Acceptance\Support
|
||||
|
||||
paths:
|
||||
tests: Tests/Acceptance
|
||||
data: Tests/Acceptance/Data
|
||||
output: .Build/web/typo3temp/var/tests/AcceptanceReports
|
||||
support: Tests/Acceptance/Support
|
||||
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunFailed
|
||||
|
||||
suites:
|
||||
acceptance:
|
||||
actor: AcceptanceTester
|
||||
path: .
|
||||
extensions:
|
||||
enabled:
|
||||
- WerkraumMedia\Watchlist\Tests\Acceptance\Support\Environment:
|
||||
typo3DatabaseUsername: 'testing'
|
||||
typo3DatabasePassword: 'testing'
|
||||
|
||||
modules:
|
||||
enabled:
|
||||
- WebDriver:
|
||||
url: 'http://localhost:8080'
|
||||
browser: 'firefox'
|
||||
restart: true
|
||||
path: ''
|
||||
capabilities:
|
||||
moz:firefoxOptions:
|
||||
args:
|
||||
- '-headless'
|
|
@ -36,6 +36,8 @@
|
|||
"typo3/cms-frontend": "^11.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.2",
|
||||
"codeception/module-webdriver": "^2.0",
|
||||
"cweagans/composer-patches": "^1.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.11",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
|
@ -59,22 +61,20 @@
|
|||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"cms-package-dir": "{$vendor-dir}/typo3/cms",
|
||||
"app-dir": ".Build",
|
||||
"extension-key": "watchlist",
|
||||
"web-dir": "public"
|
||||
}
|
||||
"web-dir": ".Build/web"
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php -r 'is_dir($extFolder=__DIR__.\"/public/typo3conf/ext/\") || mkdir($extFolder, 0777, true);'",
|
||||
"@php -r 'file_exists($extFolder=__DIR__.\"/public/typo3conf/ext/watchlist\") || symlink(__DIR__,$extFolder);'"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"typo3/testing-framework": {
|
||||
"Support cookies in request": "patches/testing-framework-cookies.patch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php -r 'is_dir($extFolder=__DIR__.\"/.Build/web/typo3conf/ext/\") || mkdir($extFolder, 0777, true);'",
|
||||
"@php -r 'file_exists($extFolder=__DIR__.\"/.Build/web/typo3conf/ext/watchlist\") || symlink(__DIR__,$extFolder);'"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ parameters:
|
|||
- Classes
|
||||
- Configuration
|
||||
- Tests
|
||||
excludePaths:
|
||||
- Tests/Acceptance/Support/_generated/
|
||||
checkMissingIterableValueType: false
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
beStrictAboutCoversAnnotation="true"
|
||||
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
forceCoversAnnotation="true"
|
||||
forceCoversAnnotation="false"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
|
@ -17,12 +17,6 @@
|
|||
verbose="false"
|
||||
>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">Classes</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>Tests/Unit/</directory>
|
||||
|
@ -32,6 +26,12 @@
|
|||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">Classes</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<php>
|
||||
<env name="typo3DatabaseDriver" value="pdo_sqlite"/>
|
||||
</php>
|
||||
|
|
94
shell.nix
Normal file
94
shell.nix
Normal file
|
@ -0,0 +1,94 @@
|
|||
{ pkgs ? import <nixpkgs> { } }:
|
||||
|
||||
let
|
||||
projectInstall = pkgs.writeShellApplication {
|
||||
name = "project-install";
|
||||
runtimeInputs = [
|
||||
pkgs.php81
|
||||
pkgs.php81Packages.composer
|
||||
];
|
||||
text = ''
|
||||
composer install --prefer-dist --no-progress --working-dir="$PROJECT_ROOT"
|
||||
'';
|
||||
};
|
||||
projectValidateComposer = pkgs.writeShellApplication {
|
||||
name = "project-validate-composer";
|
||||
runtimeInputs = [
|
||||
pkgs.php81
|
||||
pkgs.php81Packages.composer
|
||||
];
|
||||
text = ''
|
||||
composer validate
|
||||
'';
|
||||
};
|
||||
projectValidateXml = pkgs.writeShellApplication {
|
||||
name = "project-validate-xml";
|
||||
runtimeInputs = [
|
||||
pkgs.libxml2
|
||||
pkgs.wget
|
||||
projectInstall
|
||||
];
|
||||
text = ''
|
||||
project-install
|
||||
xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist
|
||||
wget --no-check-certificate https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=xliff-core-1.2-strict.xsd
|
||||
xmllint --schema xliff-core-1.2-strict.xsd --noout "$(find Resources -name '*.xlf')"
|
||||
'';
|
||||
};
|
||||
projectCodingGuideline = pkgs.writeShellApplication {
|
||||
name = "project-coding-guideline";
|
||||
runtimeInputs = [
|
||||
pkgs.php81
|
||||
projectInstall
|
||||
];
|
||||
text = ''
|
||||
project-install
|
||||
./vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||
'';
|
||||
};
|
||||
projectTestAcceptance = pkgs.writeShellApplication {
|
||||
name = "project-test-acceptance";
|
||||
runtimeInputs = [
|
||||
projectInstall
|
||||
pkgs.sqlite
|
||||
pkgs.firefox
|
||||
pkgs.geckodriver
|
||||
pkgs.php81
|
||||
];
|
||||
text = ''
|
||||
project-install
|
||||
|
||||
_instance_path="$PROJECT_ROOT/.Build/web/typo3temp/var/tests/acceptance/"
|
||||
|
||||
mkdir -p "$_instance_path"
|
||||
|
||||
geckodriver > /dev/null 2>&1 &
|
||||
_pid_geckodriver=$!
|
||||
|
||||
TYPO3_PATH_APP="$_instance_path" \
|
||||
TYPO3_PATH_ROOT="$_instance_path" \
|
||||
php -S 127.0.0.1:8080 -t "$_instance_path" > /dev/null 2>&1 &
|
||||
_pid_php=$!
|
||||
|
||||
./vendor/bin/codecept build
|
||||
./vendor/bin/codecept run
|
||||
|
||||
kill "$_pid_geckodriver" "$_pid_php"
|
||||
'';
|
||||
};
|
||||
|
||||
in pkgs.mkShell {
|
||||
name = "TYPO3 Extension Watchlist";
|
||||
buildInputs = [
|
||||
projectValidateComposer
|
||||
projectValidateXml
|
||||
projectCodingGuideline
|
||||
projectTestAcceptance
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export PROJECT_ROOT="$(pwd)"
|
||||
|
||||
export typo3DatabaseDriver=pdo_sqlite
|
||||
'';
|
||||
}
|
Loading…
Reference in a new issue