feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
286
modules/system/tests/traits/AssetMakerTest.php
Normal file
286
modules/system/tests/traits/AssetMakerTest.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
namespace System\Tests\Traits;
|
||||
|
||||
use System\Classes\Asset\PackageManager;
|
||||
use System\Tests\Bootstrap\TestCase;
|
||||
use System\Traits\AssetMaker;
|
||||
use System\Traits\EventEmitter;
|
||||
use System\Traits\ViewMaker;
|
||||
use Winter\Storm\Support\Facades\File;
|
||||
use Winter\Storm\Support\Facades\Url;
|
||||
|
||||
class AssetMakerStub
|
||||
{
|
||||
use AssetMaker;
|
||||
use ViewMaker; // Needed for `guessViewPath()`, which is used to set default assetPath
|
||||
use EventEmitter; // Needed for `addAsset()`
|
||||
}
|
||||
|
||||
class AssetMakerTest extends TestCase
|
||||
{
|
||||
private AssetMakerStub $stub;
|
||||
|
||||
private const VITE_FIXTURE_PACKAGE = 'theme-assettest';
|
||||
private const VITE_FIXTURE_THEME_PATH = '/modules/system/tests/fixtures/themes/assettest';
|
||||
private const VITE_HOT_URL = 'http://localhost:5173';
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
$this->createApplication();
|
||||
$this->stub = new AssetMakerStub();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
// Remove any vite hot file that a vite-related test wrote into the fixture
|
||||
$hotFile = base_path(self::VITE_FIXTURE_THEME_PATH . '/assets/dist/hot');
|
||||
if (File::exists($hotFile)) {
|
||||
File::delete($hotFile);
|
||||
}
|
||||
$distDir = base_path(self::VITE_FIXTURE_THEME_PATH . '/assets/dist');
|
||||
if (File::isDirectory($distDir) && count(File::files($distDir)) === 0 && count(File::directories($distDir)) === 0) {
|
||||
File::deleteDirectory($distDir);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the assettest fixture as a vite package and writes a hot file so that
|
||||
* Vite::tags() emits deterministic dev-server tags without needing a built manifest.
|
||||
*/
|
||||
private function setUpViteFixture(): void
|
||||
{
|
||||
$themePath = base_path(self::VITE_FIXTURE_THEME_PATH);
|
||||
if (!File::isDirectory($themePath)) {
|
||||
$this->markTestSkipped('Vite test fixture is missing at ' . self::VITE_FIXTURE_THEME_PATH);
|
||||
}
|
||||
|
||||
// PackageManager's lazy init() call iterates Theme::all(), which fires Halcyon model
|
||||
// events. In the full system suite, prior tests can leave plugin/datasource state in a
|
||||
// way that makes that initial iteration throw — same pattern as the existing
|
||||
// ViteInstallTest. Skip gracefully when that happens so this test is meaningful only
|
||||
// in a clean environment (and always when run in isolation).
|
||||
try {
|
||||
$packageManager = PackageManager::instance();
|
||||
} catch (\Throwable $e) {
|
||||
$this->markTestSkipped('PackageManager could not initialise in this environment: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// registerPackage silently no-ops when re-registering the same name + config.
|
||||
$packageManager->registerPackage(
|
||||
self::VITE_FIXTURE_PACKAGE,
|
||||
$themePath . '/vite.config.mjs',
|
||||
'vite'
|
||||
);
|
||||
|
||||
File::ensureDirectoryExists($themePath . '/assets/dist');
|
||||
File::put($themePath . '/assets/dist/hot', self::VITE_HOT_URL);
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
//
|
||||
|
||||
public function testGetLocalPath(): void
|
||||
{
|
||||
$basePath = base_path();
|
||||
|
||||
// Default assetPath
|
||||
$assetPath = $this->stub->guessViewPath('/assets', true);
|
||||
$resolvedPath = $this->callProtectedMethod($this->stub, 'getLocalPath', [$assetPath]);
|
||||
$this->assertEquals(realpath($basePath.$assetPath), realpath($resolvedPath));
|
||||
|
||||
// Paths with symbols
|
||||
$resolvedPath = $this->callProtectedMethod($this->stub, 'getLocalPath', ['~/themes/demo/']);
|
||||
$this->assertEquals(realpath($basePath.'/themes/demo/'), realpath($resolvedPath));
|
||||
|
||||
$resolvedPath = $this->callProtectedMethod($this->stub, 'getLocalPath', ['~/plugins/demo/']);
|
||||
$this->assertEquals(realpath($basePath.'/plugins/demo/'), realpath($resolvedPath));
|
||||
|
||||
$resolvedPath = $this->callProtectedMethod($this->stub, 'getLocalPath', ['$/demo/']);
|
||||
$this->assertEquals(realpath($basePath.'/plugins/demo/'), realpath($resolvedPath));
|
||||
|
||||
// Absolute Path
|
||||
$resolvedPath = $this->callProtectedMethod($this->stub, 'getLocalPath', [$basePath.'/some/wild/absolute/path/']);
|
||||
$this->assertEquals(realpath($basePath.'/some/wild/absolute/path/'), realpath($resolvedPath));
|
||||
}
|
||||
|
||||
public function testGetAssetPath(): void
|
||||
{
|
||||
$assetPath = 'my/path/assets';
|
||||
|
||||
$hostUrl = Url::to('/');
|
||||
|
||||
// assetPath is ignored since we use pathSymbol for plugins
|
||||
$path = $this->stub->getAssetPath('$/author/plugin/assets/js/myAsset.js', $assetPath);
|
||||
$this->assertEquals($hostUrl . 'plugins/author/plugin/assets/js/myAsset.js', $path);
|
||||
|
||||
// assetPath is ignored since we use pathSymbol for theme
|
||||
$path = $this->stub->getAssetPath('#/mytheme/assets/js/myAsset.js', $assetPath);
|
||||
$this->assertEquals($hostUrl . 'themes/mytheme/assets/js/myAsset.js', $path);
|
||||
|
||||
// assetPath is ignored since we use pathSymbol for app root
|
||||
$path = $this->stub->getAssetPath('~/plugins/author/plugin/assets/js/myAsset.js', $assetPath);
|
||||
$this->assertEquals($hostUrl . 'plugins/author/plugin/assets/js/myAsset.js', $path);
|
||||
|
||||
// assetPath is used since we use a relative path without pathSymbol
|
||||
$path = $this->stub->getAssetPath('js/myAsset.js', $assetPath);
|
||||
$this->assertEquals($hostUrl . $assetPath . '/js/myAsset.js', $path);
|
||||
|
||||
// assetPath is ignored since we use an absolute path
|
||||
$path = $this->stub->getAssetPath('/js/myAsset.js', $assetPath);
|
||||
$this->assertEquals($hostUrl . 'js/myAsset.js', $path);
|
||||
}
|
||||
|
||||
public function testAssetOrdering(): void
|
||||
{
|
||||
$hostUrl = Url::to('/');
|
||||
|
||||
// Test specified priorities
|
||||
$this->stub->addCss('mySecond.css', [
|
||||
'order' => 2,
|
||||
]);
|
||||
$this->stub->addCss('myThird.css', [
|
||||
'order' => 3,
|
||||
]);
|
||||
$this->stub->addCss('myFirst.css', [
|
||||
'order' => 1,
|
||||
]);
|
||||
|
||||
$assets = $this->stub->getAssetPaths();
|
||||
|
||||
$this->assertEquals([
|
||||
$hostUrl . 'myFirst.css',
|
||||
$hostUrl . 'mySecond.css',
|
||||
$hostUrl . 'myThird.css',
|
||||
], $assets['css']);
|
||||
|
||||
// Test first-come, first-served - these assets will be prioritised the default 100.
|
||||
$this->stub->flushAssets();
|
||||
|
||||
$this->stub->addCss('myFirst.css');
|
||||
$this->stub->addCss('mySecond.css');
|
||||
$this->stub->addCss('myThird.css');
|
||||
|
||||
$assets = $this->stub->getAssetPaths();
|
||||
|
||||
$this->assertEquals([
|
||||
$hostUrl . 'myFirst.css',
|
||||
$hostUrl . 'mySecond.css',
|
||||
$hostUrl . 'myThird.css',
|
||||
], $assets['css']);
|
||||
}
|
||||
|
||||
public function testGetAssetType(): void
|
||||
{
|
||||
$cases = [
|
||||
'foo.js' => 'js',
|
||||
'assets/javascript/theme.js' => 'js',
|
||||
'foo.css' => 'css',
|
||||
'assets/css/theme.css' => 'css',
|
||||
'foo.txt' => null,
|
||||
'foo' => null,
|
||||
'js' => null,
|
||||
'foo.JS' => 'js',
|
||||
'foo.CSS' => 'css',
|
||||
];
|
||||
|
||||
foreach ($cases as $input => $expected) {
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$this->callProtectedMethod($this->stub, 'getAssetType', [$input]),
|
||||
"getAssetType('$input') did not return the expected type"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testMakeAssetsViteCssOnly(): void
|
||||
{
|
||||
$this->setUpViteFixture();
|
||||
|
||||
$this->stub->addCss('plain.css');
|
||||
$this->stub->addVite(
|
||||
['assets/css/theme.css', 'assets/javascript/theme.js'],
|
||||
self::VITE_FIXTURE_PACKAGE
|
||||
);
|
||||
|
||||
$output = $this->stub->makeAssets('css');
|
||||
|
||||
$this->assertNotNull($output);
|
||||
// Plain CSS still emitted
|
||||
$this->assertStringContainsString('plain.css', $output);
|
||||
// Vite CSS entrypoint emitted
|
||||
$this->assertStringContainsString('assets/css/theme.css', $output);
|
||||
// Vite JS entrypoint must NOT leak into the css output
|
||||
$this->assertStringNotContainsString('assets/javascript/theme.js', $output);
|
||||
}
|
||||
|
||||
public function testMakeAssetsViteJsOnly(): void
|
||||
{
|
||||
$this->setUpViteFixture();
|
||||
|
||||
$this->stub->addJs('plain.js');
|
||||
$this->stub->addVite(
|
||||
['assets/css/theme.css', 'assets/javascript/theme.js'],
|
||||
self::VITE_FIXTURE_PACKAGE
|
||||
);
|
||||
|
||||
$output = $this->stub->makeAssets('js');
|
||||
|
||||
$this->assertNotNull($output);
|
||||
$this->assertStringContainsString('plain.js', $output);
|
||||
$this->assertStringContainsString('assets/javascript/theme.js', $output);
|
||||
$this->assertStringNotContainsString('assets/css/theme.css', $output);
|
||||
}
|
||||
|
||||
public function testMakeAssetsViteTypeUnfiltered(): void
|
||||
{
|
||||
$this->setUpViteFixture();
|
||||
|
||||
$this->stub->addVite(
|
||||
['assets/css/theme.css', 'assets/javascript/theme.js'],
|
||||
self::VITE_FIXTURE_PACKAGE
|
||||
);
|
||||
|
||||
$output = $this->stub->makeAssets('vite');
|
||||
|
||||
$this->assertNotNull($output);
|
||||
// Legacy 'vite' type must still emit both entrypoints unfiltered
|
||||
$this->assertStringContainsString('assets/css/theme.css', $output);
|
||||
$this->assertStringContainsString('assets/javascript/theme.js', $output);
|
||||
}
|
||||
|
||||
public function testMakeAssetsAllTypesSplitsViteAcrossGroups(): void
|
||||
{
|
||||
$this->setUpViteFixture();
|
||||
|
||||
$this->stub->addVite(
|
||||
['assets/css/theme.css', 'assets/javascript/theme.js'],
|
||||
self::VITE_FIXTURE_PACKAGE
|
||||
);
|
||||
|
||||
$output = $this->stub->makeAssets();
|
||||
|
||||
$this->assertNotNull($output);
|
||||
// Each entrypoint should appear exactly once: in its corresponding group, not in the
|
||||
// standalone vite block (which must NOT fire when $type is null).
|
||||
$this->assertSame(1, substr_count($output, 'assets/css/theme.css'));
|
||||
$this->assertSame(1, substr_count($output, 'assets/javascript/theme.js'));
|
||||
}
|
||||
|
||||
public function testMakeAssetsViteSkipsAssetWithNoMatchingEntrypoints(): void
|
||||
{
|
||||
$this->setUpViteFixture();
|
||||
|
||||
// JS-only entrypoint registered as a vite asset
|
||||
$this->stub->addVite(['assets/javascript/theme.js'], self::VITE_FIXTURE_PACKAGE);
|
||||
|
||||
$output = $this->stub->makeAssets('css');
|
||||
|
||||
// No css entrypoints to filter to, so the vite asset must contribute nothing
|
||||
// and not produce an empty Vite::tags() call (which would still emit @vite/client).
|
||||
$this->assertEmpty($output);
|
||||
}
|
||||
}
|
||||
144
modules/system/tests/traits/ViewMakerTest.php
Normal file
144
modules/system/tests/traits/ViewMakerTest.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace System\Tests\Traits;
|
||||
|
||||
use System\Tests\Bootstrap\TestCase;
|
||||
use System\Traits\ViewMaker;
|
||||
|
||||
class ViewMakerStub
|
||||
{
|
||||
use ViewMaker; // Needed for guessViewPath(), which is used to set default assetPath
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->layoutPath = $this->guessViewPath('/layouts');
|
||||
}
|
||||
}
|
||||
|
||||
class ViewMakerTest extends TestCase
|
||||
{
|
||||
private $stub;
|
||||
private $relativePath;
|
||||
|
||||
protected function normalizePath(string $path): string
|
||||
{
|
||||
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||
}
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
$this->createApplication();
|
||||
$this->stub = new ViewMakerStub();
|
||||
$this->relativePath = $this->normalizePath('modules/system/tests/traits/viewmakerstub');
|
||||
}
|
||||
|
||||
public function testViewPaths()
|
||||
{
|
||||
// Tests guessViewPath() and guessViewPathFrom()
|
||||
$this->assertEquals(
|
||||
$this->normalizePath(base_path($this->relativePath)),
|
||||
$this->normalizePath($this->stub->guessViewPath())
|
||||
);
|
||||
|
||||
// Request a view path first to set the default
|
||||
$path = $this->stub->getViewPath('_overridden.php');
|
||||
$this->assertEquals(
|
||||
$this->normalizePath(base_path("$this->relativePath/_overridden.php")),
|
||||
$this->normalizePath($path)
|
||||
);
|
||||
|
||||
// Test prependViewPath() & getViewPaths()
|
||||
$overridePath = "~/{$this->relativePath}override";
|
||||
$this->stub->prependViewPath($overridePath);
|
||||
$this->assertEquals(
|
||||
[
|
||||
$this->normalizePath($overridePath),
|
||||
$this->normalizePath(base_path($this->relativePath)),
|
||||
],
|
||||
array_map(function ($path) {
|
||||
return $this->normalizePath($path);
|
||||
}, $this->stub->getViewPaths())
|
||||
);
|
||||
|
||||
// Test override taking effect
|
||||
$this->assertEquals('overridden', trim($this->stub->makePartial('overridden')));
|
||||
|
||||
// Test override of PHP base file with HTM overriding file
|
||||
$this->assertEquals('overridden', trim($this->stub->makePartial('can_override_php_with_htm')));
|
||||
|
||||
// Test getViewPath()
|
||||
$paths = [
|
||||
'non_existant' => 'non_existant',
|
||||
'view.php' => base_path($this->relativePath . '/view.php'),
|
||||
'_overridden.php' => base_path($this->relativePath . 'override/_overridden.php'),
|
||||
'_can_override_php_with_htm.php' => base_path($this->relativePath . 'override/_can_override_php_with_htm.htm'),
|
||||
"~/{$this->relativePath}/symbols.php" => base_path($this->relativePath . '/symbols.php'),
|
||||
];
|
||||
|
||||
foreach ($paths as $path => $expected) {
|
||||
$this->assertEquals($this->normalizePath($expected), $this->normalizePath($this->stub->getViewPath($path)));
|
||||
}
|
||||
}
|
||||
|
||||
public function testMakePartial()
|
||||
{
|
||||
$sectionContent = '<!-- Section -->' . PHP_EOL
|
||||
. '<div class="field-section">' . PHP_EOL
|
||||
. ' <h4>label</h4>' . PHP_EOL
|
||||
. ' ' . PHP_EOL
|
||||
. ' <p class="help-block">comment</p>' . PHP_EOL
|
||||
. ' </div>';
|
||||
|
||||
// Test various paths that are accepted when rendering a partial
|
||||
$partials = [
|
||||
'relative_no_ext' => 'relative no ext',
|
||||
'folder/no_ext' => 'folder no ext',
|
||||
"{$this->relativePath}/relative_no_ext" => 'relative no ext',
|
||||
"~/{$this->relativePath}/_relative_no_ext.php" => 'relative no ext',
|
||||
"~/{$this->relativePath}/symbols.htm" => 'symbols content',
|
||||
"~/{$this->relativePath}/symbols.php" => 'symbols content',
|
||||
"~/{$this->relativePath}/specific.htm" => 'explicit htm path, but actually php',
|
||||
'modules/backend/widgets/form/partials/field_section' => $sectionContent,
|
||||
'~/modules/backend/widgets/form/partials/_field_section.htm' => $sectionContent,
|
||||
];
|
||||
|
||||
foreach ($partials as $partial => $expected) {
|
||||
$contents = $this->stub->makePartial($partial, [
|
||||
'field' => (object) [
|
||||
'label' => 'label',
|
||||
'comment' => 'comment',
|
||||
'commentHtml' => false
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($expected, trim($contents));
|
||||
}
|
||||
}
|
||||
|
||||
public function testMakeView()
|
||||
{
|
||||
$this->stub->layout = 'default';
|
||||
$this->assertEquals('layout contents-view contents', trim($this->stub->makeView('view')));
|
||||
}
|
||||
|
||||
public function testMakeViewContent()
|
||||
{
|
||||
// No layout set, return contents unmodified
|
||||
$this->stub->layout = '';
|
||||
$this->assertEquals('input', $this->stub->makeViewContent('input'));
|
||||
|
||||
// Layout set, return contents rendered in layout
|
||||
$this->stub->layout = 'default';
|
||||
$this->assertEquals('layout contents-input', trim($this->stub->makeViewContent('input')));
|
||||
}
|
||||
|
||||
public function testMakeLayout()
|
||||
{
|
||||
$this->stub->layout = 'default';
|
||||
$this->assertEquals('layout contents-', trim($this->stub->makeLayout()));
|
||||
}
|
||||
|
||||
public function testMakeLayoutPartial()
|
||||
{
|
||||
$this->assertEquals('layout partial contents', trim($this->stub->makeLayoutPartial('layout_partial')));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
original
|
||||
@@ -0,0 +1 @@
|
||||
original
|
||||
@@ -0,0 +1 @@
|
||||
relative no ext
|
||||
@@ -0,0 +1 @@
|
||||
folder no ext
|
||||
@@ -0,0 +1 @@
|
||||
layout partial contents
|
||||
@@ -0,0 +1 @@
|
||||
layout contents-<?= Block::get('body'); ?>
|
||||
1
modules/system/tests/traits/viewmakerstub/specific.php
Normal file
1
modules/system/tests/traits/viewmakerstub/specific.php
Normal file
@@ -0,0 +1 @@
|
||||
explicit htm path, but actually php
|
||||
1
modules/system/tests/traits/viewmakerstub/symbols.php
Normal file
1
modules/system/tests/traits/viewmakerstub/symbols.php
Normal file
@@ -0,0 +1 @@
|
||||
symbols content
|
||||
1
modules/system/tests/traits/viewmakerstub/view.php
Normal file
1
modules/system/tests/traits/viewmakerstub/view.php
Normal file
@@ -0,0 +1 @@
|
||||
view contents
|
||||
@@ -0,0 +1 @@
|
||||
overridden
|
||||
@@ -0,0 +1 @@
|
||||
overridden
|
||||
Reference in New Issue
Block a user