diff --git a/.github/workflows/pre-merge-test.yaml b/.github/workflows/pre-merge-test.yaml
new file mode 100644
index 0000000..afd42f6
--- /dev/null
+++ b/.github/workflows/pre-merge-test.yaml
@@ -0,0 +1,32 @@
+
+# Workflow for running smoke tests for pull requests etc.
+
+name: Pre merge tests
+
+on:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '16'
+
+ - name: Setup PHP with composer v2
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '7.4'
+ tools: composer:v2
+
+ - name: Install composer dependencies.
+ run: composer install
+
+ - name: Run unit tests
+ run: composer test
diff --git a/.gitignore b/.gitignore
index b29496c..b91a192 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,7 @@ dist
# track these files, if they exist
!.gitignore
!.editorconfig
+!.github
+
+# PHPUnit
+.phpunit.result.cache
\ No newline at end of file
diff --git a/README.md b/README.md
index 54045f8..a59b76e 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@
- [Built With](#built-with)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
+ - [Tests](#tests)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
@@ -65,13 +66,31 @@ nano setup.json
```sh
php setup.php
```
-4. Install and build NPM packages
+4. Install composer packages
+```sh
+composer install
+```
+5. Install and build NPM packages
```sh
npm install && npm run build
```
-5. Install composer packages
+6. Build assets
```sh
-composer install
+npm run build
+```
+### Tests
+
+1. Install composer packages
+```sh
+composer insall
+```
+2. Run unit tests
+```
+composer test
+```
+3. For code coverage
+```
+composer coverage
```
## Roadmap
diff --git a/build.php b/build.php
index 7c58993..cd691dd 100644
--- a/build.php
+++ b/build.php
@@ -7,10 +7,11 @@
// Any command needed to run and build plugin assets when newly cheched out of repo.
$buildCommands = [
- 'npm install --no-progress',
+ 'npm ci --no-progress',
'npx browserslist@latest --update-db',
'npm run build',
- 'composer install --prefer-dist --no-progress'
+ 'composer install --prefer-dist --no-progress --no-dev',
+ 'composer dump-autoload --no-dev --classmap-authoritative'
];
// Files and directories not suitable for prod to be removed.
@@ -24,7 +25,10 @@
'webpack.config.js',
'node_modules',
'package-lock.json',
- 'package.json'
+ 'package.json',
+ 'patchwork.json',
+ 'phpunit.xml',
+ 'source/tests'
];
$dirName = basename(dirname(__FILE__));
diff --git a/composer.json b/composer.json
index b1b35f5..12cd990 100644
--- a/composer.json
+++ b/composer.json
@@ -3,18 +3,25 @@
"description": "{{BPREPLACEDESCRIPTION}}",
"type": "wordpress-plugin",
"license": "MIT",
+ "scripts": {
+ "test": "./vendor/bin/phpunit --testdox",
+ "coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --testdox",
+ "minimal": "./vendor/bin/phpunit"
+ },
"authors": [
{
"name": "{{BPREPLACEAUTHOR}}",
"email": "{{BPREPLACEAUTHOREMAIL}}"
}
],
+ "autoload": {
+ "psr-4": {"{{BPREPLACENAMESPACE}}\\": "source/php/"}
+ },
"minimum-stability": "stable",
"require": {},
- "repositories": [
- {
- "type": "vcs",
- "url": "https://github.com/{{BPREPLACEGITHUB}}/modularity-{{BPREPLACESLUG}}.git"
- }
- ]
+ "require-dev": {
+ "brain/monkey": "^2.6",
+ "codedungeon/phpunit-result-printer": "^0.31.0",
+ "phpunit/phpunit": "^9.5"
+ }
}
diff --git a/modularity-boilerplate.php b/modularity-boilerplate.php
index fe23924..1952715 100755
--- a/modularity-boilerplate.php
+++ b/modularity-boilerplate.php
@@ -22,19 +22,15 @@
define('{{BPREPLACECAPSCONSTANT}}_URL', plugins_url('', __FILE__));
define('{{BPREPLACECAPSCONSTANT}}_TEMPLATE_PATH', {{BPREPLACECAPSCONSTANT}}_PATH . 'templates/');
define('{{BPREPLACECAPSCONSTANT}}_VIEW_PATH', {{BPREPLACECAPSCONSTANT}}_PATH . 'views/');
-define('{{BPREPLACECAPSCONSTANT}}_MODULE_VIEW_PATH', plugin_dir_path(__FILE__) . 'source/php/Module/views');
+define('{{BPREPLACECAPSCONSTANT}}_MODULE_VIEW_PATH', {{BPREPLACECAPSCONSTANT}}_PATH . 'source/php/Module/views');
define('{{BPREPLACECAPSCONSTANT}}_MODULE_PATH', {{BPREPLACECAPSCONSTANT}}_PATH . 'source/php/Module/');
load_plugin_textdomain('modularity-{{BPREPLACESLUG}}', false, plugin_basename(dirname(__FILE__)) . '/languages');
-require_once {{BPREPLACECAPSCONSTANT}}_PATH . 'source/php/Vendor/Psr4ClassLoader.php';
require_once {{BPREPLACECAPSCONSTANT}}_PATH . 'Public.php';
-// Instantiate and register the autoloader
-$loader = new {{BPREPLACENAMESPACE}}\Vendor\Psr4ClassLoader();
-$loader->addPrefix('{{BPREPLACENAMESPACE}}', {{BPREPLACECAPSCONSTANT}}_PATH);
-$loader->addPrefix('{{BPREPLACENAMESPACE}}', {{BPREPLACECAPSCONSTANT}}_PATH . 'source/php/');
-$loader->register();
+// Register the autoloader
+require __DIR__ . '/vendor/autoload.php';
// Acf auto import and export
$acfExportManager = new \AcfExportManager\AcfExportManager();
diff --git a/patchwork.json b/patchwork.json
new file mode 100644
index 0000000..686abd3
--- /dev/null
+++ b/patchwork.json
@@ -0,0 +1,7 @@
+{
+ "redefinable-internals": [
+ "file_exists",
+ "file_get_contents",
+ "function_exists"
+ ]
+}
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..6b97f68
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ ./source/php
+
+
+
+
+
+
+
+
+ ./source/tests/php
+
+
+
\ No newline at end of file
diff --git a/setup.json b/setup.json
index 390d4f6..eb94964 100644
--- a/setup.json
+++ b/setup.json
@@ -7,5 +7,5 @@
"capsConstant": "MODULARITY_BOILERPLATE",
"author": "Firstname Lastname @ Company",
"authoremail": "firstname.lastname@company.com",
- "github": "githubname"
+ "github": "helsingborg-stad"
}
\ No newline at end of file
diff --git a/source/php/Helper/CacheBust.php b/source/php/Helper/CacheBust.php
index 5613c37..a9d5251 100755
--- a/source/php/Helper/CacheBust.php
+++ b/source/php/Helper/CacheBust.php
@@ -7,12 +7,21 @@ class CacheBust
/**
* Returns the revved/cache-busted file name of an asset.
* @param string $name Asset name (array key) from rev-mainfest.json
- * @param boolean $returnName Returns $name if set to true while in dev mode
* @return string filename of the asset (including directory above)
*/
- public static function name($name, $returnName = true)
+ public function name($name)
{
- $revManifest = self::getRevManifest();
+ $jsonPath = {{BPREPLACECAPSCONSTANT}}_PATH . apply_filters(
+ '{{BPREPLACENAMESPACE}}/Helper/CacheBust/RevManifestPath',
+ 'dist/manifest.json'
+ );
+
+ $revManifest = [];
+ if (file_exists($jsonPath)) {
+ $revManifest = json_decode(file_get_contents($jsonPath), true);
+ } elseif ($this->isDebug()) {
+ echo '
Error: Assets not built. Go to ' . {{BPREPLACECAPSCONSTANT}}_PATH . ' and run gulp. See ' . {{BPREPLACECAPSCONSTANT}}_PATH . 'README.md for more info.
';
+ }
if (!isset($revManifest[$name])) {
return;
@@ -22,20 +31,10 @@ public static function name($name, $returnName = true)
}
/**
- * Decode assets json to array
- * @return array containg assets filenames
+ * Check if debug mode, Remove constant dependency in tests.
*/
- public static function getRevManifest()
+ public function isDebug()
{
- $jsonPath = {{BPREPLACECAPSCONSTANT}}_PATH . apply_filters(
- '{{BPREPLACENAMESPACE}}/Helper/CacheBust/RevManifestPath',
- 'dist/manifest.json'
- );
-
- if (file_exists($jsonPath)) {
- return json_decode(file_get_contents($jsonPath), true);
- } elseif (WP_DEBUG) {
- echo 'Error: Assets not built. Go to ' . {{BPREPLACECAPSCONSTANT}}_PATH . ' and run gulp. See ' . {{BPREPLACECAPSCONSTANT}}_PATH . 'README.md for more info.
';
- }
+ return defined('WP_DEBUG') && WP_DEBUG;
}
}
diff --git a/source/php/Module/Boilerplate.php b/source/php/Module/Boilerplate.php
index b96f7dc..d87fc59 100644
--- a/source/php/Module/Boilerplate.php
+++ b/source/php/Module/Boilerplate.php
@@ -2,8 +2,6 @@
namespace {{BPREPLACENAMESPACE}}\Module;
-use {{BPREPLACENAMESPACE}}\Helper\CacheBust;
-
/**
* Class {{BPREPLACESLUGCAMELCASE}}
* @package {{BPREPLACESLUGCAMELCASE}}\Module
@@ -18,6 +16,8 @@ public function init()
$this->nameSingular = __("{{BPREPLACESLUGCAMELCASE}}", 'modularity-{{BPREPLACESLUG}}');
$this->namePlural = __("{{BPREPLACESLUGCAMELCASE}}", 'modularity-{{BPREPLACESLUG}}');
$this->description = __("{{BPREPLACEDESCRIPTION}}", 'modularity-{{BPREPLACESLUG}}');
+
+ $this->cacheBust = new \{{BPREPLACENAMESPACE}}\Helper\CacheBust();
}
/**
@@ -26,12 +26,10 @@ public function init()
*/
public function data(): array
{
- $data = array();
-
//Append field config
- $data = array_merge($data, (array) \Modularity\Helper\FormatObject::camelCase(
+ $data = (array) \Modularity\Helper\FormatObject::camelCase(
get_fields($this->ID)
- ));
+ );
//Translations
$data['lang'] = (object) array(
@@ -62,7 +60,7 @@ public function style()
//Register custom css
wp_register_style(
'modularity-{{BPREPLACESLUG}}',
- {{BPREPLACECAPSCONSTANT}}_URL . '/dist/' . CacheBust::name('css/modularity-{{BPREPLACESLUG}}.css'),
+ {{BPREPLACECAPSCONSTANT}}_URL . '/dist/' . $this->cacheBust->name('css/modularity-{{BPREPLACESLUG}}.css'),
null,
'1.0.0'
);
@@ -80,7 +78,7 @@ public function script()
//Register custom css
wp_register_script(
'modularity-{{BPREPLACESLUG}}',
- {{BPREPLACECAPSCONSTANT}}_URL . '/dist/' . CacheBust::name('js/modularity-{{BPREPLACESLUG}}.js'),
+ {{BPREPLACECAPSCONSTANT}}_URL . '/dist/' . $this->cacheBust->name('js/modularity-{{BPREPLACESLUG}}.js'),
null,
'1.0.0'
);
diff --git a/source/php/Vendor/Psr4ClassLoader.php b/source/php/Vendor/Psr4ClassLoader.php
deleted file mode 100755
index 8e89f31..0000000
--- a/source/php/Vendor/Psr4ClassLoader.php
+++ /dev/null
@@ -1,89 +0,0 @@
-
- */
-class Psr4ClassLoader
-{
- /**
- * @var array
- */
- private $prefixes = array();
-
- /**
- * @param string $prefix
- * @param string $baseDir
- */
- public function addPrefix($prefix, $baseDir)
- {
- $prefix = trim($prefix, '\\').'\\';
- $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
- $this->prefixes[] = array($prefix, $baseDir);
- }
-
- /**
- * @param string $class
- *
- * @return string|null
- */
- public function findFile($class)
- {
- $class = ltrim($class, '\\');
- foreach ($this->prefixes as $current) {
- list($currentPrefix, $currentBaseDir) = $current;
- if (0 === strpos($class, $currentPrefix)) {
- $classWithoutPrefix = substr($class, strlen($currentPrefix));
- $file = $currentBaseDir.str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutPrefix).'.php';
-
- if (file_exists($file)) {
- return $file;
- }
- }
- }
- }
-
- /**
- * @param string $class
- *
- * @return bool
- */
- public function loadClass($class)
- {
- $file = $this->findFile($class);
- if (null !== $file) {
- require $file;
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Registers this instance as an autoloader.
- *
- * @param bool $prepend
- */
- public function register($prepend = false)
- {
- spl_autoload_register(array($this, 'loadClass'), true, $prepend);
- }
-
- /**
- * Removes this instance from the registered autoloaders.
- */
- public function unregister()
- {
- spl_autoload_unregister(array($this, 'loadClass'));
- }
-}
diff --git a/source/tests/includes/PluginTestCase.php b/source/tests/includes/PluginTestCase.php
new file mode 100644
index 0000000..baf5d4a
--- /dev/null
+++ b/source/tests/includes/PluginTestCase.php
@@ -0,0 +1,42 @@
+returnArg(1);
+ Monkey\Functions\when('_e')
+ ->returnArg(1);
+ Monkey\Functions\when('_n')
+ ->returnArg(1);
+ }
+
+ /**
+ * Teardown which calls \WP_Mock tearDown
+ *
+ * @return void
+ */
+ public function tearDown(): void
+ {
+ Monkey\tearDown();
+ parent::tearDown();
+ }
+}
diff --git a/source/tests/includes/bootstrap.php b/source/tests/includes/bootstrap.php
new file mode 100644
index 0000000..f30de68
--- /dev/null
+++ b/source/tests/includes/bootstrap.php
@@ -0,0 +1,19 @@
+addPsr4('{{BPREPLACENAMESPACE}}\\Test\\', __DIR__ . '/../php/');
+
+require_once __DIR__ . '/PluginTestCase.php';
diff --git a/source/tests/php/Admin/SettingsTest.php b/source/tests/php/Admin/SettingsTest.php
new file mode 100644
index 0000000..0ac77ea
--- /dev/null
+++ b/source/tests/php/Admin/SettingsTest.php
@@ -0,0 +1,35 @@
+registerSettings()'));
+ }
+
+ public function testRegisterSettings()
+ {
+ Functions\expect('acf_add_options_sub_page')->once()->with(
+ array(
+ 'page_title' => __("{{BPREPLACENAME}}", 'modularity-{{BPREPLACESLUG}}'),
+ 'menu_title' => __("{{BPREPLACENAME}} Settings", 'modularity-{{BPREPLACESLUG}}'),
+ 'menu_slug' => 'modularity-{{BPREPLACESLUG}}-settings',
+ 'parent_slug' => 'options-general.php',
+ 'capability' => 'manage_options'
+ )
+ );
+
+ $settings = new Settings();
+
+ $settings->registerSettings();
+ }
+}
diff --git a/source/tests/php/AppTest.php b/source/tests/php/AppTest.php
new file mode 100644
index 0000000..0ab578c
--- /dev/null
+++ b/source/tests/php/AppTest.php
@@ -0,0 +1,70 @@
+createPartialMock(
+ AppTest::class,
+ ['modularity_register_module']
+ );
+ parent::setUp();
+ }
+
+ public function testAddHooks()
+ {
+ new App();
+
+ self::assertNotFalse(has_action('plugins_loaded', '{{BPREPLACENAMESPACE}}\App->registerModule()'));
+ self::assertNotFalse(has_filter('Municipio/blade/view_paths', '{{BPREPLACENAMESPACE}}\App->addViewPaths()'));
+ }
+
+ public function testAddViewPaths()
+ {
+ $path = '/test';
+ Functions\when('is_child_theme')->justReturn(false);
+ $app = new App();
+ $viewPaths = $app->addViewPaths([$path]);
+ $this->assertSame(end($viewPaths), $path);
+ }
+
+ public function testAddViewPathsInChildTheme()
+ {
+ $path = '/test';
+ Functions\when('is_child_theme')->justReturn(true);
+ $app = new App();
+ $viewPaths = $app->addViewPaths([$path]);
+ $this->assertSame($viewPaths[0], $path);
+ }
+
+ public function testRegisterModule()
+ {
+ Functions\when('function_exists')->justReturn(true);
+
+ self::$functions->expects($this->once())->method('modularity_register_module');
+ $app = new App();
+ $app->registerModule();
+ }
+
+ // Helper function for mocking global function(Must break coding standard).
+ public function modularity_register_module()
+ {
+ return;
+ }
+}
+
+// Helper function for mocking global function.
+function modularity_register_module()
+{
+ return AppTest::$functions->modularity_register_module();
+}
diff --git a/source/tests/php/Helper/CacheBustTest.php b/source/tests/php/Helper/CacheBustTest.php
new file mode 100644
index 0000000..146050a
--- /dev/null
+++ b/source/tests/php/Helper/CacheBustTest.php
@@ -0,0 +1,35 @@
+justReturn(false);
+
+ $cacheBust = Mockery::mock('{{BPREPLACENAMESPACE}}\Helper\CacheBust')->makePartial();
+ $cacheBust->shouldReceive('isDebug')->andReturn(true);
+
+ $realfile = $cacheBust->name('nofile');
+
+ // Just expect some output.
+ $this->expectOutputRegex('/^.+$/');
+ $this->assertSame($realfile, null);
+ }
+
+ public function testReturnRealFileWhenFoundInManifest()
+ {
+ Functions\when('file_exists')->justReturn(true);
+ Functions\when('file_get_contents')->justReturn('{"file.js": "realfile.js"}');
+
+ $cacheBust = new CacheBust();
+ $realfile = $cacheBust->name('file.js');
+
+ $this->assertSame($realfile, 'realfile.js');
+ }
+}
diff --git a/source/tests/php/Module/BoilerplateTest.php b/source/tests/php/Module/BoilerplateTest.php
new file mode 100644
index 0000000..fe2f514
--- /dev/null
+++ b/source/tests/php/Module/BoilerplateTest.php
@@ -0,0 +1,96 @@
+init();
+
+ $this->assertSame(
+ ${{BPREPLACESLUG}}->nameSingular,
+ __("{{BPREPLACESLUGCAMELCASE}}", 'modularity-{{BPREPLACESLUG}}')
+ );
+ $this->assertSame(
+ ${{BPREPLACESLUG}}->namePlural,
+ __("{{BPREPLACESLUGCAMELCASE}}", 'modularity-{{BPREPLACESLUG}}')
+ );
+ $this->assertSame(
+ ${{BPREPLACESLUG}}->description,
+ __("{{BPREPLACEDESCRIPTION}}", 'modularity-{{BPREPLACESLUG}}')
+ );
+ }
+
+ public function testData()
+ {
+ $testData = [
+ 'testconfig' => 'testconfig',
+ 'lang' => (object) [
+ 'info' => __(
+ "Hey! This is your new {{BPREPLACENAME}} module. Let's get going.",
+ 'modularity-{{BPREPLACESLUG}}'
+ )
+ ]
+ ];
+
+ // Mock parent class that is loaded outside of plugin.
+ Mockery::mock('\Modularity\Module');
+ $formatObject = Mockery::mock('overload:\Modularity\Helper\FormatObject');
+ $formatObject->shouldReceive('camelCase')->once()->andReturn($testData);
+
+
+
+ Functions\when('get_fields')->justReturn(false);
+
+ ${{BPREPLACESLUG}} = new {{BPREPLACESLUGCAMELCASE}}();
+ ${{BPREPLACESLUG}}->ID = 1;
+
+
+
+ $data = ${{BPREPLACESLUG}}->data();
+
+ $this->assertSame($data['testconfig'], 'testconfig');
+ $this->assertSame((array) $data['lang'], (array) $testData['lang']);
+ }
+
+ public function testTemplate()
+ {
+ // Mock parent class that is loaded outside of plugin.
+ Mockery::mock('\Modularity\Module');
+ ${{BPREPLACESLUG}} = new {{BPREPLACESLUGCAMELCASE}}();
+ $this->assertSame(${{BPREPLACESLUG}}->template(), '{{BPREPLACESLUG}}.blade.php');
+ }
+
+ public function testStyle()
+ {
+ Functions\expect('wp_register_style')->once();
+ Functions\expect('wp_enqueue_style')->once()->with('modularity-{{BPREPLACESLUG}}');
+
+ // Mock parent class that is loaded outside of plugin.
+ Mockery::mock('\Modularity\Module');
+ ${{BPREPLACESLUG}} = new {{BPREPLACESLUGCAMELCASE}}();
+ ${{BPREPLACESLUG}}->init();
+ ${{BPREPLACESLUG}}->style();
+ }
+
+ public function testScript()
+ {
+ Functions\expect('wp_register_script')->once();
+ Functions\expect('wp_enqueue_script')->once()->with('modularity-{{BPREPLACESLUG}}');
+
+ // Mock parent class that is loaded outside of plugin.
+ Mockery::mock('\Modularity\Module');
+
+ ${{BPREPLACESLUG}} = new {{BPREPLACESLUGCAMELCASE}}();
+ ${{BPREPLACESLUG}}->init();
+ ${{BPREPLACESLUG}}->script();
+ }
+}