WIP: VERSION: 03.07.00 > Link asset minification to Joomla cache system #62
@@ -105,8 +105,8 @@ Moko-Cassiopeia supports custom color schemes for both light and dark modes:
|
||||
- **Custom**: Create your own custom colors by adding `colors_custom.css` files
|
||||
|
||||
To use custom colors:
|
||||
1. Create `src/media/css/colors/light/colors_custom.css` for light mode
|
||||
2. Create `src/media/css/colors/dark/colors_custom.css` for dark mode
|
||||
1. Create `media/templates/site/moko-cassiopeia/css/colors/light/colors_custom.css` for light mode
|
||||
2. Create `media/templates/site/moko-cassiopeia/css/colors/dark/colors_custom.css` for dark mode
|
||||
3. Define your CSS variables in these files (see existing `colors_standard.css` for reference)
|
||||
4. Select "Custom" in template settings under **Variables & Palettes**
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ MOKO-CASSIOPEIA="MOKO-CASSIOPEIA Site template"
|
||||
TPL_MOKO-CASSIOPEIA_XML_DESCRIPTION="<h3>MOKO-CASSIOPEIA Template Description</h3> <p> <strong>MOKO-CASSIOPEIA 3.0</strong> continues Joomla’s tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
|
||||
|
||||
; ===== System / layout =====
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL="Development Mode"
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC="If 'Development Mode' is active, certain features may be disabled, such as Google Tag Manager and Google Analytics."
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL="Development Mode (Deprecated)"
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC="This setting is deprecated. Asset minification is now controlled by Joomla's cache system (System > Global Configuration > System > Cache Settings). When cache is enabled, minified assets are used. When cache is disabled, non-minified assets are used for debugging."
|
||||
TPL_MOKO-CASSIOPEIA_FLUID_LABEL="Layout"
|
||||
TPL_MOKO-CASSIOPEIA_STATIC="Static"
|
||||
TPL_MOKO-CASSIOPEIA_FLUID="Fluid"
|
||||
|
||||
@@ -16,8 +16,8 @@ MOKO-CASSIOPEIA="MOKO-CASSIOPEIA Site template"
|
||||
TPL_MOKO-CASSIOPEIA_XML_DESCRIPTION="<h3>MOKO-CASSIOPEIA Template Description</h3> <p> <strong>MOKO-CASSIOPEIA 3.0</strong> continues Joomla’s tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
|
||||
|
||||
; ===== System / layout =====
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL="Development Mode"
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC="If 'Development Mode' is active, certain features may be disabled, such as Google Tag Manager and Google Analytics."
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL="Development Mode (Deprecated)"
|
||||
TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC="This setting is deprecated. Asset minification is now controlled by Joomla's cache system (System > Global Configuration > System > Cache Settings). When cache is enabled, minified assets are used. When cache is disabled, non-minified assets are used for debugging."
|
||||
TPL_MOKO-CASSIOPEIA_FLUID_LABEL="Layout"
|
||||
TPL_MOKO-CASSIOPEIA_STATIC="Static"
|
||||
TPL_MOKO-CASSIOPEIA_FLUID="Fluid"
|
||||
|
||||
1
src/media/css/colors/dark/colors_alternative.min.css
vendored
Normal file
1
src/media/css/colors/dark/colors_alternative.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/css/colors/dark/colors_standard.min.css
vendored
Normal file
1
src/media/css/colors/dark/colors_standard.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/css/colors/light/colors_alternative.min.css
vendored
Normal file
1
src/media/css/colors/light/colors_alternative.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/css/colors/light/colors_standard.min.css
vendored
Normal file
1
src/media/css/colors/light/colors_standard.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/css/editor.min.css
vendored
Normal file
1
src/media/css/editor.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@charset "UTF-8";body{font-size:1rem;font-weight:400;line-height:1.5;color:#22262a;background-color:#fff}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem;font-weight:700;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}h2{font-size:calc(1.325rem + 0.9vw)}h3{font-size:calc(1.3rem + 0.6vw)}h4{font-size:calc(1.275rem + 0.3vw)}h5{font-size:1.25rem}h6{font-size:1rem}a{text-decoration:none}a:link{color:#224faa}a:hover{color:#424077}p{margin-top:0;margin-bottom:1rem}hr#system-readmore{color:#f00;border:#f00 dashed 1px}span[lang]{padding:2px;border:1px dashed #bbb}span[lang]:after{font-size:smaller;color:#f00;vertical-align:super;content:attr(lang)}
|
||||
1
src/media/css/template.min.css
vendored
Normal file
1
src/media/css/template.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
19
src/media/css/user.css
Normal file
19
src/media/css/user.css
Normal file
@@ -0,0 +1,19 @@
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: Moko-Cassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||
PATH: ./media/css/user.css
|
||||
VERSION: 03.05.00
|
||||
BRIEF: User custom styles - add your custom CSS here
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is intentionally empty and available for user customizations.
|
||||
* Add your custom CSS rules here to override template styles.
|
||||
*/
|
||||
0
src/media/css/user.min.css
vendored
Normal file
0
src/media/css/user.min.css
vendored
Normal file
1
src/media/js/darkmode-toggle.min.js
vendored
Normal file
1
src/media/js/darkmode-toggle.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(){'use strict';var STORAGE_KEY='theme';var docEl=document.documentElement;var mql=window.matchMedia('(prefers-color-scheme: dark)');function getStored(){try{return localStorage.getItem(STORAGE_KEY);}catch(e){return null;}}function setStored(v){try{localStorage.setItem(STORAGE_KEY,v);}catch(e){}}function clearStored(){try{localStorage.removeItem(STORAGE_KEY);}catch(e){}}function systemTheme(){return mql.matches ? 'dark' : 'light';}function applyTheme(theme){docEl.setAttribute('data-bs-theme',theme);docEl.setAttribute('data-aria-theme',theme);var meta=document.querySelector('meta[name="theme-color"]');if(meta){meta.setAttribute('content',theme==='dark' ? '#0f1115' : '#ffffff');}var sw=document.getElementById('mokoThemeSwitch');if(sw){sw.setAttribute('aria-checked',theme==='dark' ? 'true' : 'false');}}function initTheme(){var stored=getStored();applyTheme(stored ? stored : systemTheme());}function posClassFromBody(){var pos=(document.body.getAttribute('data-theme-fab-pos')||'br').toLowerCase();if(!/^(br|bl|tr|tl)$/.test(pos))pos='br';return 'pos-'+pos;}function buildToggle(){if(document.getElementById('mokoThemeFab'))return;var wrap=document.createElement('div');wrap.id='mokoThemeFab';wrap.className=posClassFromBody();var lblL=document.createElement('span');lblL.className='label';lblL.textContent='Light';var switchWrap=document.createElement('button');switchWrap.id='mokoThemeSwitch';switchWrap.type='button';switchWrap.setAttribute('role','switch');switchWrap.setAttribute('aria-label','Toggle dark mode');switchWrap.setAttribute('aria-checked','false');var track=document.createElement('span');track.className='switch';var knob=document.createElement('span');knob.className='knob';track.appendChild(knob);switchWrap.appendChild(track);var lblD=document.createElement('span');lblD.className='label';lblD.textContent='Dark';var auto=document.createElement('button');auto.id='mokoThemeAuto';auto.type='button';auto.className='btn btn-sm btn-link text-decoration-none px-2';auto.setAttribute('aria-label','Follow system theme');auto.textContent='Auto';switchWrap.addEventListener('click',function(){var current=(docEl.getAttribute('data-bs-theme')||'light').toLowerCase();var next=current==='dark' ? 'light' : 'dark';applyTheme(next);setStored(next);});auto.addEventListener('click',function(){clearStored();applyTheme(systemTheme());});var onMql=function(){if(!getStored())applyTheme(systemTheme());};if(typeof mql.addEventListener==='function')mql.addEventListener('change',onMql);else if(typeof mql.addListener==='function')mql.addListener(onMql);var initial=getStored()||systemTheme();switchWrap.setAttribute('aria-checked',initial==='dark' ? 'true' : 'false');wrap.appendChild(lblL);wrap.appendChild(switchWrap);wrap.appendChild(lblD);wrap.appendChild(auto);document.body.appendChild(wrap);window.mokoThemeFabStatus=function(){var el=document.getElementById('mokoThemeFab');if(!el)return{mounted: false};var r=el.getBoundingClientRect();return{mounted: true,rect:{top: r.top,left: r.left,width: r.width,height: r.height},zIndex: window.getComputedStyle(el).zIndex,posClass: el.className};};setTimeout(function(){var r=wrap.getBoundingClientRect();if(r.width<10||r.height<10){wrap.classList.add('debug-outline');console.warn('[moko]Theme FAB mounted but appears too small — check CSS collisions.');}},50);}function init(){initTheme();buildToggle();}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}})();
|
||||
1
src/media/js/gtm.min.js
vendored
Normal file
1
src/media/js/gtm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/media/js/template.min.js
vendored
Normal file
1
src/media/js/template.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(win,doc){"use strict";function backToTop(){win.scrollTo({top: 0,behavior: "smooth"});}function handleScroll(){if(win.scrollY>50){doc.body.classList.add("scrolled");}else{doc.body.classList.remove("scrolled");}}function initTOC(){if(typeof win.Toc==="function"&&doc.querySelector("#toc")){win.Toc.init({$nav: $("#toc"),$scope: $("main")});}}function initDrawers(){var leftBtn=doc.querySelector(".drawer-toggle-left");var rightBtn=doc.querySelector(".drawer-toggle-right");if(leftBtn){leftBtn.addEventListener("click",function(){var target=doc.querySelector(leftBtn.getAttribute("data-bs-target"));if(target)new bootstrap.Offcanvas(target).show();});}if(rightBtn){rightBtn.addEventListener("click",function(){var target=doc.querySelector(rightBtn.getAttribute("data-bs-target"));if(target)new bootstrap.Offcanvas(target).show();});}}function initBackTop(){var backTop=doc.getElementById("back-top");if(backTop){backTop.addEventListener("click",function(e){e.preventDefault();backToTop();});}}function init(){handleScroll();win.addEventListener("scroll",handleScroll);initTOC();initDrawers();initBackTop();}if(doc.readyState==="loading"){doc.addEventListener("DOMContentLoaded",init);}else{init();}})(window,document);
|
||||
1
src/media/js/theme-init.min.js
vendored
Normal file
1
src/media/js/theme-init.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(win,doc){"use strict";var storageKey="theme";var mql=win.matchMedia("(prefers-color-scheme: dark)");var root=doc.documentElement;function applyTheme(theme){root.setAttribute("data-bs-theme",theme);root.setAttribute("data-aria-theme",theme);try{localStorage.setItem(storageKey,theme);}catch(e){}}function clearStored(){try{localStorage.removeItem(storageKey);}catch(e){}}function systemTheme(){return mql.matches ? "dark" : "light";}function init(){var stored=null;try{stored=localStorage.getItem(storageKey);}catch(e){}var theme=stored ? stored : systemTheme();applyTheme(theme);var onChange=function(){if(!localStorage.getItem(storageKey)){applyTheme(systemTheme());}};if(typeof mql.addEventListener==="function"){mql.addEventListener("change",onChange);}else if(typeof mql.addListener==="function"){mql.addListener(onChange);}var switchEl=doc.getElementById("themeSwitch");var autoBtn=doc.getElementById("themeAuto");if(switchEl){switchEl.checked=(theme==="dark");switchEl.addEventListener("change",function(){var choice=switchEl.checked ? "dark" : "light";applyTheme(choice);});}if(autoBtn){autoBtn.addEventListener("click",function(){clearStored();applyTheme(systemTheme());});}}if(doc.readyState==="loading"){doc.addEventListener("DOMContentLoaded",init);}else{init();}})(window,document);
|
||||
230
src/templates/AssetMinifier.php
Normal file
230
src/templates/AssetMinifier.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: Moko-Cassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||
PATH: ./templates/moko-cassiopeia/AssetMinifier.php
|
||||
VERSION: 03.07.00
|
||||
BRIEF: Asset minification helper linked to Joomla cache system
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
/**
|
||||
* Asset Minifier Helper
|
||||
*
|
||||
* Handles minification and cleanup of CSS and JavaScript assets
|
||||
* based on the Joomla cache system setting.
|
||||
*
|
||||
* IMPORTANT NOTES:
|
||||
* - This is a BASIC minifier suitable for cache-based switching
|
||||
* - For production builds, consider using professional tools like:
|
||||
* * CSS: cssnano, clean-css
|
||||
* * JavaScript: terser, uglify-js, closure-compiler
|
||||
* - URL preservation in JS is best-effort; complex cases may fail
|
||||
* - String content preservation is basic; edge cases may exist
|
||||
* - Does not handle complex string scenarios or regex patterns
|
||||
*
|
||||
* The minifier is designed to be "good enough" for automatic switching
|
||||
* based on Joomla's cache setting, not for optimal compression.
|
||||
*
|
||||
* BEHAVIOR:
|
||||
* - When Joomla cache is ENABLED: Uses minified (.min) files for performance
|
||||
* - When Joomla cache is DISABLED: Uses non-minified files for debugging
|
||||
*/
|
||||
class AssetMinifier
|
||||
{
|
||||
/**
|
||||
* Minify CSS content
|
||||
*
|
||||
* @param string $css CSS content to minify
|
||||
* @return string Minified CSS
|
||||
*/
|
||||
public static function minifyCSS(string $css): string
|
||||
{
|
||||
// Remove comments
|
||||
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
||||
|
||||
// Remove all whitespace and newlines
|
||||
$css = preg_replace('/\s+/', ' ', $css);
|
||||
|
||||
// Remove spaces around selectors and properties
|
||||
$css = preg_replace('/\s*([{}|:;,])\s*/', '$1', $css);
|
||||
|
||||
// Remove trailing semicolons
|
||||
$css = str_replace(';}', '}', $css);
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify JavaScript content
|
||||
*
|
||||
* Note: This is a basic minifier. For production use, consider using
|
||||
* a more sophisticated minifier like terser or uglify-js.
|
||||
*
|
||||
* @param string $js JavaScript content to minify
|
||||
* @return string Minified JavaScript
|
||||
*/
|
||||
public static function minifyJS(string $js): string
|
||||
{
|
||||
// Remove single-line comments but preserve URLs (https://, http://)
|
||||
// The negative lookbehind (?<![:\'"a-zA-Z0-9]) ensures we don't match:
|
||||
// - URLs: https://example.com (preceded by :)
|
||||
// - String literals: "//comment" (preceded by quote)
|
||||
// - Protocol-relative URLs: //example.com (preceded by non-alphanumeric)
|
||||
$js = preg_replace('~(?<![:\'"a-zA-Z0-9])//[^\n]*\n~', "\n", $js);
|
||||
|
||||
// Remove multi-line comments
|
||||
$js = preg_replace('~/\*.*?\*/~s', '', $js);
|
||||
|
||||
// Normalize whitespace to single spaces
|
||||
$js = preg_replace('/\s+/', ' ', $js);
|
||||
|
||||
// Remove spaces around operators and punctuation (but keep spaces in strings)
|
||||
$js = preg_replace('/\s*([\{\}\[\]\(\);,=<>!&|+\-*\/])\s*/', '$1', $js);
|
||||
|
||||
return trim($js);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create minified version of a file
|
||||
*
|
||||
* @param string $sourcePath Path to source file
|
||||
* @param string $destPath Path to minified file
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function minifyFile(string $sourcePath, string $destPath): bool
|
||||
{
|
||||
if (!file_exists($sourcePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = file_get_contents($sourcePath);
|
||||
if ($content === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ext = pathinfo($sourcePath, PATHINFO_EXTENSION);
|
||||
|
||||
if ($ext === 'css') {
|
||||
$minified = self::minifyCSS($content);
|
||||
} elseif ($ext === 'js') {
|
||||
$minified = self::minifyJS($content);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_put_contents($destPath, $minified) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all minified files in a directory (recursive)
|
||||
* Excludes vendor directory to preserve pre-minified vendor assets
|
||||
*
|
||||
* @param string $dir Directory path
|
||||
* @return int Number of files deleted
|
||||
*/
|
||||
public static function deleteMinifiedFiles(string $dir): int
|
||||
{
|
||||
$deleted = 0;
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && preg_match('/\.min\.(css|js)$/', $file->getFilename())) {
|
||||
// Skip vendor files as they come pre-minified from vendors
|
||||
if (strpos($file->getPathname(), '/vendor/') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unlink($file->getPathname())) {
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process assets based on cache setting
|
||||
*
|
||||
* When $useNonMinified is true (cache disabled), deletes .min files and uses source files
|
||||
* When $useNonMinified is false (cache enabled), creates .min files and uses them
|
||||
*
|
||||
* @param string $mediaPath Path to media directory
|
||||
* @param bool $useNonMinified Whether to use non-minified files (true when cache disabled)
|
||||
* @return array Status information
|
||||
*/
|
||||
public static function processAssets(string $mediaPath, bool $useNonMinified): array
|
||||
{
|
||||
$result = [
|
||||
'mode' => $useNonMinified ? 'cache-disabled' : 'cache-enabled',
|
||||
'minified' => 0,
|
||||
'deleted' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
if (!is_dir($mediaPath)) {
|
||||
$result['errors'][] = "Media path does not exist: {$mediaPath}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($useNonMinified) {
|
||||
// Cache disabled: Delete all .min files and use non-minified sources
|
||||
$result['deleted'] = self::deleteMinifiedFiles($mediaPath);
|
||||
} else {
|
||||
// Cache enabled: Create minified versions of CSS and JS files for performance
|
||||
// NOTE: This list is hardcoded for predictability and to ensure only
|
||||
// specific template files are minified. Vendor files are excluded as
|
||||
// they come pre-minified. If you add new template assets, add them here.
|
||||
$files = [
|
||||
'css/template.css' => 'css/template.min.css',
|
||||
'css/user.css' => 'css/user.min.css',
|
||||
'css/editor.css' => 'css/editor.min.css',
|
||||
'css/colors/light/colors_standard.css' => 'css/colors/light/colors_standard.min.css',
|
||||
'css/colors/light/colors_alternative.css' => 'css/colors/light/colors_alternative.min.css',
|
||||
'css/colors/light/colors_custom.css' => 'css/colors/light/colors_custom.min.css',
|
||||
'css/colors/dark/colors_standard.css' => 'css/colors/dark/colors_standard.min.css',
|
||||
'css/colors/dark/colors_alternative.css' => 'css/colors/dark/colors_alternative.min.css',
|
||||
'css/colors/dark/colors_custom.css' => 'css/colors/dark/colors_custom.min.css',
|
||||
'js/template.js' => 'js/template.min.js',
|
||||
'js/theme-init.js' => 'js/theme-init.min.js',
|
||||
'js/darkmode-toggle.js' => 'js/darkmode-toggle.min.js',
|
||||
'js/gtm.js' => 'js/gtm.min.js',
|
||||
];
|
||||
|
||||
foreach ($files as $source => $dest) {
|
||||
$sourcePath = $mediaPath . '/' . $source;
|
||||
$destPath = $mediaPath . '/' . $dest;
|
||||
|
||||
// Only minify if source exists and dest doesn't exist or is older
|
||||
if (file_exists($sourcePath)) {
|
||||
if (!file_exists($destPath) || filemtime($sourcePath) > filemtime($destPath)) {
|
||||
if (self::minifyFile($sourcePath, $destPath)) {
|
||||
$result['minified']++;
|
||||
} else {
|
||||
$result['errors'][] = "Failed to minify: {$source}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
INGROUP: Moko-Cassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||
PATH: ./templates/moko-cassiopeia/index.php
|
||||
VERSION: 03.06.00
|
||||
VERSION: 03.07.00
|
||||
BRIEF: Main template index file for Moko-Cassiopeia rendering site layout
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,9 @@ use Joomla\CMS\Component\ComponentHelper;
|
||||
|
||||
/** @var Joomla\CMS\Document\HtmlDocument $this */
|
||||
|
||||
// Load Asset Minifier
|
||||
require_once __DIR__ . '/AssetMinifier.php';
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$input = $app->getInput();
|
||||
$document = $app->getDocument();
|
||||
@@ -41,8 +44,16 @@ $params_googleanalytics = $this->params->get('googleanalytics', false);
|
||||
$params_googleanalyticsid = $this->params->get('googleanalyticsid', null);
|
||||
$params_custom_head_start = $this->params->get('custom_head_start', null);
|
||||
$params_custom_head_end = $this->params->get('custom_head_end', null);
|
||||
$params_developmentmode = $this->params->get('developmentmode', false);
|
||||
/*
|
||||
|
||||
// Check if Joomla cache is enabled (use minified assets when cache is on)
|
||||
$cacheEnabled = (bool) $app->get('caching', 0);
|
||||
|
||||
// Process assets based on cache setting
|
||||
// When cache is enabled, use minified assets for performance
|
||||
// When cache is disabled, use non-minified assets for debugging
|
||||
$mediaPath = JPATH_ROOT . '/media/templates/site/moko-cassiopeia';
|
||||
AssetMinifier::processAssets($mediaPath, !$cacheEnabled);
|
||||
|
||||
// Bootstrap behaviors (assets handled via WAM)
|
||||
HTMLHelper::_('bootstrap.framework');
|
||||
HTMLHelper::_('bootstrap.alert');
|
||||
@@ -81,55 +92,60 @@ $this->setTitle($final);
|
||||
// Template/Media path
|
||||
$templatePath = 'media/templates/site/moko-cassiopeia';
|
||||
|
||||
// Asset suffix based on Joomla cache setting
|
||||
// When cache is enabled, use minified (.min) files for performance
|
||||
// When cache is disabled, use non-minified files for debugging
|
||||
$assetSuffix = $cacheEnabled ? '.min' : '';
|
||||
|
||||
// ===========================
|
||||
// Web Asset Manager (WAM) — matches your joomla.asset.json
|
||||
// ===========================
|
||||
|
||||
// Core template CSS
|
||||
$wa->useStyle('template.base'); // css/template.css
|
||||
$wa->useStyle('template.global.base' . $assetSuffix); // css/template.css or template.min.css
|
||||
$wa->useStyle('template.user' . $assetSuffix); // css/user.css or user.min.css
|
||||
|
||||
// Optional vendor CSS
|
||||
$wa->useStyle('vendor.bootstrap-toc' . $assetSuffix);
|
||||
|
||||
// Optional demo/showcase CSS (available for use, not loaded by default)
|
||||
// To use: Add 'template.global.social-media-demo' to your article/module
|
||||
// $wa->useStyle('template.global.social-media-demo');
|
||||
|
||||
// Color theme (light + optional dark)
|
||||
$colorLightKey = strtolower(preg_replace('/[^a-z0-9_.-]/i', '', $params_LightColorName));
|
||||
$colorDarkKey = strtolower(preg_replace('/[^a-z0-9_.-]/i', '', $params_DarkColorName));
|
||||
$lightKey = 'template.light.' . $colorLightKey;
|
||||
$darkKey = 'template.dark.' . $colorDarkKey;
|
||||
|
||||
$lightKey = 'template.light.' . $colorLightKey . $assetSuffix;
|
||||
$darkKey = 'template.dark.' . $colorDarkKey . $assetSuffix;
|
||||
try {
|
||||
$wa->useStyle('template.light.colors_standard');
|
||||
$wa->useStyle('template.light.colors_standard' . $assetSuffix);
|
||||
} catch (\Throwable $e) {
|
||||
$wa->registerAndUseStyle('template.light.colors_standard', $templatePath . '/css/global/light/colors_standard.css');
|
||||
$wa->registerAndUseStyle('template.light.colors_standard', $templatePath . '/css/colors/light/colors_standard' . $assetSuffix . '.css');
|
||||
}
|
||||
|
||||
try {
|
||||
$wa->useStyle('template.dark.colors_standard');
|
||||
$wa->useStyle('template.dark.colors_standard' . $assetSuffix);
|
||||
} catch (\Throwable $e) {
|
||||
$wa->registerAndUseStyle('template.dark.colors_standard', $templatePath . '/css/global/dark/colors_standard.css');
|
||||
$wa->registerAndUseStyle('template.dark.colors_standard', $templatePath . '/css/colors/dark/colors_standard' . $assetSuffix . '.css');
|
||||
}
|
||||
|
||||
try {
|
||||
$wa->useStyle($lightKey);
|
||||
} catch (\Throwable $e) {
|
||||
$wa->registerAndUseStyle('template.light.dynamic', $templatePath . '/css/global/light/' . $colorLightKey . '.css');
|
||||
$wa->registerAndUseStyle('template.light.dynamic', $templatePath . '/css/colors/light/' . $colorLightKey . $assetSuffix . '.css');
|
||||
}
|
||||
|
||||
try {
|
||||
$wa->useStyle($darkKey);
|
||||
} catch (\Throwable $e) {
|
||||
$wa->registerAndUseStyle('template.dark.dynamic', $templatePath . '/css/global/dark/' . $colorDarkKey . '.css');
|
||||
$wa->registerAndUseStyle('template.dark.dynamic', $templatePath . '/css/colors/dark/' . $colorDarkKey . $assetSuffix . '.css');
|
||||
}
|
||||
|
||||
// Scripts
|
||||
$wa->useScript('template.js');
|
||||
|
||||
/**
|
||||
* VirtueMart detection:
|
||||
* - Component must exist and be enabled
|
||||
*/
|
||||
$isVirtueMartActive = ComponentHelper::isEnabled('com_virtuemart', true);
|
||||
|
||||
if ($isVirtueMartActive) {
|
||||
/**
|
||||
* Load a VirtueMart-specific stylesheet defined in your template manifest.
|
||||
* This assumes you defined an asset named "template.virtuemart".
|
||||
*/
|
||||
$wa->useStyle('vendor.vm');
|
||||
}
|
||||
$wa->useScript('template.js' . $assetSuffix);
|
||||
$wa->useScript('theme-init.js' . $assetSuffix);
|
||||
$wa->useScript('darkmode-toggle.js' . $assetSuffix);
|
||||
$wa->useScript('vendor.bootstrap-toc.js' . $assetSuffix);
|
||||
|
||||
// Font scheme (external or local) + CSS custom properties
|
||||
$params_FontScheme = $this->params->get('useFontScheme', false);
|
||||
@@ -209,40 +225,21 @@ if ($this->params->get('faKitCode')) {
|
||||
HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']);
|
||||
} else {
|
||||
try {
|
||||
if($params_developmentmode){
|
||||
$wa->useStyle('vendor.fa7free.all');
|
||||
$wa->useStyle('vendor.fa7free.brands');
|
||||
$wa->useStyle('vendor.fa7free.fontawesome');
|
||||
$wa->useStyle('vendor.fa7free.regular');
|
||||
$wa->useStyle('vendor.fa7free.solid');
|
||||
} else {
|
||||
$wa->useStyle('vendor.fa7free.all.min');
|
||||
$wa->useStyle('vendor.fa7free.brands.min');
|
||||
$wa->useStyle('vendor.fa7free.fontawesome.min');
|
||||
$wa->useStyle('vendor.fa7free.regular.min');
|
||||
$wa->useStyle('vendor.fa7free.solid.min');
|
||||
}
|
||||
$wa->useStyle('vendor.fa7free.all' . $assetSuffix);
|
||||
$wa->useStyle('vendor.fa7free.brands' . $assetSuffix);
|
||||
$wa->useStyle('vendor.fa7free.fontawesome' . $assetSuffix);
|
||||
$wa->useStyle('vendor.fa7free.regular' . $assetSuffix);
|
||||
$wa->useStyle('vendor.fa7free.solid' . $assetSuffix);
|
||||
} catch (\Throwable $e) {
|
||||
if($params_developmentmode){
|
||||
$wa->registerAndUseStyle('vendor.fa7free.all.dynamic', $templatePath . '/vendor/fa7free/css/all.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.brands.dynamic', $templatePath . '/vendor/fa7free/css/brands.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.regular.dynamic', $templatePath . '/vendor/fa7free/css/regular.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.solid.dynamic', $templatePath . '/vendor/fa7free/css/solid.css');
|
||||
} else {
|
||||
$wa->registerAndUseStyle('vendor.fa7free.all.min.dynamic', $templatePath . '/vendor/fa7free/css/all.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.brands.min.dynamic', $templatePath . '/vendor/fa7free/css/brands.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.min.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.regular.min.dynamic', $templatePath . '/vendor/fa7free/css/regular.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.solid.min.dynamic', $templatePath . '/vendor/fa7free/css/solid.min.css');
|
||||
}
|
||||
|
||||
$wa->registerAndUseStyle('vendor.fa7free.all.dynamic', $templatePath . '/vendor/fa7free/css/all' . $assetSuffix . '.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.brands.dynamic', $templatePath . '/vendor/fa7free/css/brands' . $assetSuffix . '.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome' . $assetSuffix . '.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.regular.dynamic', $templatePath . '/vendor/fa7free/css/regular' . $assetSuffix . '.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.solid.dynamic', $templatePath . '/vendor/fa7free/css/solid' . $assetSuffix . '.css');
|
||||
}
|
||||
}
|
||||
$params_leftIcon = htmlspecialchars($this->params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_COMPAT, 'UTF-8');
|
||||
$params_rightIcon = htmlspecialchars($this->params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
$wa->useStyle('template.user'); // css/user.css
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
INGROUP: Moko-Cassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia
|
||||
PATH: ./templates/moko-cassiopeia/offline.php
|
||||
VERSION: 03.06.00
|
||||
VERSION: 03.07.00
|
||||
BRIEF: Offline page template file for Moko-Cassiopeia
|
||||
*/
|
||||
|
||||
@@ -32,23 +32,37 @@ use Joomla\CMS\Uri\Uri;
|
||||
* @var string $this->direction
|
||||
*/
|
||||
|
||||
// Load Asset Minifier
|
||||
require_once __DIR__ . '/AssetMinifier.php';
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$doc = Factory::getDocument();
|
||||
$params = $this->params ?: $app->getTemplate(true)->params;
|
||||
$direction = $this->direction ?: 'ltr';
|
||||
|
||||
/* -----------------------
|
||||
Load ONLY template.css + colors_*.css (with min toggle)
|
||||
Load ONLY template.css + colors_*.css (with min toggle based on cache)
|
||||
------------------------ */
|
||||
$useMin = !((int) $params->get('development_mode', 0) === 1);
|
||||
$assetSuffix = $useMin ? '.min' : '';
|
||||
$base = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/css/';
|
||||
// Check if Joomla cache is enabled (use minified assets when cache is on)
|
||||
$cacheEnabled = (bool) $app->get('caching', 0);
|
||||
$assetSuffix = $cacheEnabled ? '.min' : '';
|
||||
|
||||
// Process assets based on cache setting
|
||||
// When cache is enabled, use minified assets for performance
|
||||
// When cache is disabled, use non-minified assets for debugging
|
||||
$mediaPath = JPATH_ROOT . '/media/templates/site/moko-cassiopeia';
|
||||
AssetMinifier::processAssets($mediaPath, !$cacheEnabled);
|
||||
|
||||
$base = rtrim(Uri::root(true), '/') . '/media/templates/site/moko-cassiopeia/css/';
|
||||
|
||||
$doc->addStyleSheet($base . 'template' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-template']);
|
||||
/* If you have a template param for color variant, set it here; defaults to 'standard' */
|
||||
$colorKey = (string) ($params->get('colors', 'standard') ?: 'standard');
|
||||
$colorKey = (string) ($params->get('colorLightName', 'colors_standard') ?: 'colors_standard');
|
||||
$colorKey = preg_replace('~[^a-z0-9_-]~i', '', $colorKey);
|
||||
$doc->addStyleSheet($base . 'colors_' . $colorKey . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-colors']);
|
||||
$doc->addStyleSheet($base . 'colors/light/' . $colorKey . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-colors-light']);
|
||||
$colorKeyDark = (string) ($params->get('colorDarkName', 'colors_standard') ?: 'colors_standard');
|
||||
$colorKeyDark = preg_replace('~[^a-z0-9_-]~i', '', $colorKeyDark);
|
||||
$doc->addStyleSheet($base . 'colors/dark/' . $colorKeyDark . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-colors-dark']);
|
||||
|
||||
/* Bootstrap CSS/JS for accordion behavior; safe to keep. */
|
||||
HTMLHelper::_('bootstrap.loadCss', true, $doc);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
DEFGROUP: Joomla
|
||||
INGROUP: Moko-Cassiopeia
|
||||
PATH: templates/moko-cassiopeia/templateDetails.xml
|
||||
VERSION: 03.06.00
|
||||
VERSION: 03.07.00
|
||||
BRIEF: Template manifest XML file for Moko-Cassiopeia
|
||||
=========================================================================
|
||||
-->
|
||||
@@ -22,8 +22,8 @@
|
||||
</server>
|
||||
</updateservers>
|
||||
<name>moko-cassiopeia</name>
|
||||
<version>03.06.00</version>
|
||||
<creationDate>2025-12-23</creationDate>
|
||||
<version>03.07.00</version>
|
||||
<creationDate>2026-01-29</creationDate>
|
||||
<author>Jonathan Miller || Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<copyright>(C)GNU General Public License Version 3 - 2025 Moko Consulting</copyright>
|
||||
@@ -87,12 +87,10 @@
|
||||
<fields name="params">
|
||||
<!-- Advanced tab (non-theme/system options only) -->
|
||||
<fieldset name="advanced">
|
||||
<!--
|
||||
<field name="developmentmode" type="radio" label="TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL" description="TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC" default="1" layout="joomla.form.field.radio.switcher" filter="boolean">
|
||||
<field name="developmentmode" type="radio" label="TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_LABEL" description="TPL_MOKO-CASSIOPEIA_DEVELOPMENTMODE_DESC" default="0" layout="joomla.form.field.radio.switcher" filter="boolean">
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
-->
|
||||
<field name="fluidContainer" type="radio" layout="joomla.form.field.radio.switcher" default="0" label="TPL_MOKO-CASSIOPEIA_FLUID_LABEL">
|
||||
<option value="0">TPL_MOKO-CASSIOPEIA_STATIC</option>
|
||||
<option value="1">TPL_MOKO-CASSIOPEIA_FLUID</option>
|
||||
|
||||
240
tests/unit/AssetMinifierTest.php
Normal file
240
tests/unit/AssetMinifierTest.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Codeception\Test\Unit;
|
||||
use Tests\Support\UnitTester;
|
||||
|
||||
/**
|
||||
* Unit tests for AssetMinifier class
|
||||
*/
|
||||
class AssetMinifierTest extends Unit
|
||||
{
|
||||
protected UnitTester $tester;
|
||||
|
||||
private string $testDir;
|
||||
private string $testCssFile;
|
||||
private string $testJsFile;
|
||||
|
||||
protected function _before()
|
||||
{
|
||||
// Create temporary test directory
|
||||
$this->testDir = sys_get_temp_dir() . '/moko-cassiopeia-test-' . uniqid();
|
||||
mkdir($this->testDir, 0777, true);
|
||||
|
||||
$this->testCssFile = $this->testDir . '/test.css';
|
||||
$this->testJsFile = $this->testDir . '/test.js';
|
||||
|
||||
// Load the AssetMinifier class
|
||||
require_once __DIR__ . '/../../src/templates/AssetMinifier.php';
|
||||
}
|
||||
|
||||
protected function _after()
|
||||
{
|
||||
// Clean up test directory
|
||||
if (is_dir($this->testDir)) {
|
||||
$this->deleteDirectory($this->testDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to recursively delete a directory
|
||||
*/
|
||||
private function deleteDirectory(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS minification
|
||||
*/
|
||||
public function testMinifyCSS()
|
||||
{
|
||||
$css = "
|
||||
/* This is a comment */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
";
|
||||
|
||||
$minified = \AssetMinifier::minifyCSS($css);
|
||||
|
||||
// Should remove comments
|
||||
$this->assertStringNotContainsString('/* This is a comment */', $minified);
|
||||
|
||||
// Should remove whitespace
|
||||
$this->assertStringNotContainsString("\n", $minified);
|
||||
$this->assertStringNotContainsString(" ", $minified);
|
||||
|
||||
// Should still contain the actual CSS
|
||||
$this->assertStringContainsString('body{', $minified);
|
||||
$this->assertStringContainsString('margin:0', $minified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JavaScript minification
|
||||
*/
|
||||
public function testMinifyJS()
|
||||
{
|
||||
$js = "
|
||||
// This is a single-line comment
|
||||
function hello() {
|
||||
/* Multi-line
|
||||
comment */
|
||||
console.log('Hello World');
|
||||
return true;
|
||||
}
|
||||
";
|
||||
|
||||
$minified = \AssetMinifier::minifyJS($js);
|
||||
|
||||
// Should remove comments
|
||||
$this->assertStringNotContainsString('// This is a single-line comment', $minified);
|
||||
$this->assertStringNotContainsString('/* Multi-line', $minified);
|
||||
|
||||
// Should still contain the function
|
||||
$this->assertStringContainsString('function hello()', $minified);
|
||||
$this->assertStringContainsString("console.log('Hello World')", $minified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test minifying CSS file
|
||||
*/
|
||||
public function testMinifyCSSFile()
|
||||
{
|
||||
$css = "body { margin: 0; padding: 0; }";
|
||||
file_put_contents($this->testCssFile, $css);
|
||||
|
||||
$minFile = $this->testDir . '/test.min.css';
|
||||
$result = \AssetMinifier::minifyFile($this->testCssFile, $minFile);
|
||||
|
||||
$this->assertTrue($result, 'Minification should succeed');
|
||||
$this->assertFileExists($minFile, 'Minified file should exist');
|
||||
|
||||
$content = file_get_contents($minFile);
|
||||
$this->assertNotEmpty($content, 'Minified file should not be empty');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test minifying JavaScript file
|
||||
*/
|
||||
public function testMinifyJSFile()
|
||||
{
|
||||
$js = "function test() { return true; }";
|
||||
file_put_contents($this->testJsFile, $js);
|
||||
|
||||
$minFile = $this->testDir . '/test.min.js';
|
||||
$result = \AssetMinifier::minifyFile($this->testJsFile, $minFile);
|
||||
|
||||
$this->assertTrue($result, 'Minification should succeed');
|
||||
$this->assertFileExists($minFile, 'Minified file should exist');
|
||||
|
||||
$content = file_get_contents($minFile);
|
||||
$this->assertNotEmpty($content, 'Minified file should not be empty');
|
||||
$this->assertStringContainsString('function test()', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test minifying non-existent file
|
||||
*/
|
||||
public function testMinifyNonExistentFile()
|
||||
{
|
||||
$result = \AssetMinifier::minifyFile(
|
||||
$this->testDir . '/nonexistent.css',
|
||||
$this->testDir . '/output.min.css'
|
||||
);
|
||||
|
||||
$this->assertFalse($result, 'Should return false for non-existent file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting minified files
|
||||
*/
|
||||
public function testDeleteMinifiedFiles()
|
||||
{
|
||||
// Create some test files
|
||||
file_put_contents($this->testDir . '/file1.css', 'body{}');
|
||||
file_put_contents($this->testDir . '/file1.min.css', 'body{}');
|
||||
file_put_contents($this->testDir . '/file2.js', 'var x=1;');
|
||||
file_put_contents($this->testDir . '/file2.min.js', 'var x=1;');
|
||||
|
||||
// Create subdirectory with minified files
|
||||
$subDir = $this->testDir . '/sub';
|
||||
mkdir($subDir);
|
||||
file_put_contents($subDir . '/sub.min.css', 'div{}');
|
||||
|
||||
$deleted = \AssetMinifier::deleteMinifiedFiles($this->testDir);
|
||||
|
||||
$this->assertGreaterThanOrEqual(3, $deleted, 'Should delete at least 3 minified files');
|
||||
$this->assertFileDoesNotExist($this->testDir . '/file1.min.css');
|
||||
$this->assertFileDoesNotExist($this->testDir . '/file2.min.js');
|
||||
$this->assertFileDoesNotExist($subDir . '/sub.min.css');
|
||||
|
||||
// Non-minified files should still exist
|
||||
$this->assertFileExists($this->testDir . '/file1.css');
|
||||
$this->assertFileExists($this->testDir . '/file2.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test process assets with cache disabled (use non-minified)
|
||||
*/
|
||||
public function testProcessAssetsCacheDisabled()
|
||||
{
|
||||
// Create some minified files
|
||||
file_put_contents($this->testDir . '/test.min.css', 'body{}');
|
||||
file_put_contents($this->testDir . '/test.min.js', 'var x=1;');
|
||||
|
||||
// When cache is disabled, useNonMinified = true
|
||||
$result = \AssetMinifier::processAssets($this->testDir, true);
|
||||
|
||||
$this->assertEquals('cache-disabled', $result['mode']);
|
||||
$this->assertGreaterThanOrEqual(2, $result['deleted'], 'Should delete minified files');
|
||||
$this->assertFileDoesNotExist($this->testDir . '/test.min.css');
|
||||
$this->assertFileDoesNotExist($this->testDir . '/test.min.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test process assets with cache enabled (use minified)
|
||||
*/
|
||||
public function testProcessAssetsCacheEnabled()
|
||||
{
|
||||
// Create source files
|
||||
file_put_contents($this->testDir . '/test.css', 'body { margin: 0; }');
|
||||
file_put_contents($this->testDir . '/test.js', 'function test() { return true; }');
|
||||
|
||||
// When cache is enabled, useNonMinified = false
|
||||
// This will try to minify files in the hardcoded list, which won't match our test files
|
||||
// So we just verify the mode is set correctly
|
||||
$result = \AssetMinifier::processAssets($this->testDir, false);
|
||||
|
||||
$this->assertEquals('cache-enabled', $result['mode']);
|
||||
$this->assertEquals(0, $result['minified'], 'Should not minify test files (not in hardcoded list)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test process assets returns error for non-existent directory
|
||||
*/
|
||||
public function testProcessAssetsNonExistentDirectory()
|
||||
{
|
||||
$result = \AssetMinifier::processAssets('/nonexistent/path', false);
|
||||
|
||||
$this->assertNotEmpty($result['errors']);
|
||||
$this->assertStringContainsString('does not exist', $result['errors'][0]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user