diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 4037284..3f128f2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -18,6 +18,7 @@ jobs:
fail-fast: false
matrix:
php: [ "8.1", "8.2" ]
+ laravel: [ "^9.0", "^10.0" ]
dependency-version: [ prefer-lowest, prefer-stable ]
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
@@ -30,6 +31,8 @@ jobs:
with:
php-version: ${{ matrix.php }}
- name: 🔮 Install dependencies
- run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
+ run: |
+ composer require "laravel/framework:${{ matrix.laravel }}" --dev --no-interaction --no-update
+ composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
- name: 🕵️♂️ Run Pest Tests
run: composer test
diff --git a/README.md b/README.md
index e8e4597..6e3fec5 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,76 @@
-# tailwind-merge-php
+
![Social Card of Tailwind Merge PHP](./art/socialcard.jpg)
+
+# Efficiently merge Tailwind CSS classes in PHP without style conflicts
[![Latest Version](https://img.shields.io/github/release/yieldstudio/tailwind-merge-php?style=flat-square)](https://github.com/yieldstudio/tailwind-merge-php/releases)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/yieldstudio/tailwind-merge-php/tests.yml?branch=main)](https://github.com/yieldstudio/tailwind-merge-php/actions/workflows/tests.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/yieldstudio/tailwind-merge-php?style=flat-square)](https://packagist.org/packages/yieldstudio/tailwind-merge-php)
-Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com) classes in PHP without style conflicts.
-
-> Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
+This package allows you to merge multiple [Tailwind CSS](https://tailwindcss.com) classes and automatically resolves conflicts between them without headaches.
tailwind-merge-php is a PHP port of the excellent [tailwind-merge](https://github.com/dcastil/tailwind-merge) created by [dcastil](https://github.com/dcastil).
-
- Supports Tailwind v3.0 up to v3.3
-- First-class support for laravel
+- First-class support for [Laravel](https://laravel.com/)
+
+## Installation
+
+ composer require yieldstudio/tailwind-merge-php
+
+## Usage
```php
use YieldStudio\TailwindMerge\TailwindMerge;
+use YieldStudio\TailwindMerge\TailwindMergeConfig;
-TailwindMerge::instance()->merge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
-// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
+// Singleton
+$twMerge = TailwindMerge::instance();
+$twMerge->merge('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg
-tw_merge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
-// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
+// Custom instance
+$twMerge = new TailwindMerge(TailwindMergeConfig::default()); // Config is optional
+$twMerge->merge('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg
```
-> ✍️ Complete documentation is being written
+## Laravel Support
-## Installation
- composer require yieldstudio/tailwind-merge-php
+You can publish the configuration file with:
+
+```shell
+php artisan vendor:publish --provider="YieldStudio\TailwindMerge\Laravel\TailwindMergeServiceProvider"
+```
+
+### Using the `tw` helper
+
+```php
+tw('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg
+```
+### Using Blade directive
+
+```php
+
+
+// will be
+
+```
+
+### Using Blade components
+
+```php
+// avatar.blade.php
+tw('w-8 h-8 rounded-full') }}>
+
+// header.blade.php
+
+
+// will be
+
+```
+
+## Configuration & plugins
+
+> ✍️ Complete documentation is being written
## Unit tests
diff --git a/art/socialcard.jpg b/art/socialcard.jpg
new file mode 100644
index 0000000..0b1ed26
Binary files /dev/null and b/art/socialcard.jpg differ
diff --git a/composer.json b/composer.json
index 29bb063..1ebd303 100644
--- a/composer.json
+++ b/composer.json
@@ -14,9 +14,6 @@
"php"
],
"autoload": {
- "files": [
- "src/helpers.php"
- ],
"psr-4": {
"YieldStudio\\TailwindMerge\\": "src/"
}
@@ -38,17 +35,17 @@
"types": "phpstan analyse"
},
"require": {
- "php": ">=8.1",
- "illuminate/collections": "^10.4"
+ "php": ">=8.1"
},
"require-dev": {
- "pestphp/pest": "^2.2",
- "illuminate/support": "^10.4",
+ "pestphp/pest": "^v1|^v2.2.3",
"laravel/pint": "^1.10",
- "phpstan/phpstan": "^1.10"
+ "phpstan/phpstan": "^1.10",
+ "laravel/framework": "^9|^10",
+ "orchestra/testbench": "7.*|8.*"
},
"suggest": {
- "illuminate/support": "Required to use with Laravel."
+ "laravel/framework": "Want to use Tailwind Merge PHP with Laravel ?"
},
"config": {
"allow-plugins": {
diff --git a/src/ClassContext.php b/src/ClassContext.php
new file mode 100644
index 0000000..28ddc1c
--- /dev/null
+++ b/src/ClassContext.php
@@ -0,0 +1,18 @@
+validators->push(new ClassValidator(
+ $classPart->validators[] = new ClassValidator(
$classGroupId,
$classDefinition
- ));
+ );
continue;
}
@@ -72,18 +72,14 @@ protected static function getPart(ClassPart $classPart, string $path): ClassPart
$currentClassPartObject = $classPart;
foreach (explode(self::CLASS_PART_SEPARATOR, $path) as $pathPart) {
- if (!$currentClassPartObject) {
- return $classPart;
- }
-
- if (! $currentClassPartObject->nextPart->has($pathPart)) {
- $currentClassPartObject->nextPart->put($pathPart, new ClassPart());
+ if (! array_key_exists($pathPart, $currentClassPartObject->nextPart)) {
+ $currentClassPartObject->nextPart[$pathPart] = new ClassPart();
}
- $currentClassPartObject = $currentClassPartObject->nextPart->get($pathPart);
+ $currentClassPartObject = $currentClassPartObject->nextPart[$pathPart];
}
- return $currentClassPartObject ?? $classPart;
+ return $currentClassPartObject;
}
protected static function getPrefixedClassGroups(array $classGroups, ?string $prefix): array
diff --git a/src/ClassPart.php b/src/ClassPart.php
index c02a448..70965e1 100644
--- a/src/ClassPart.php
+++ b/src/ClassPart.php
@@ -4,25 +4,20 @@
namespace YieldStudio\TailwindMerge;
-use Illuminate\Support\Collection;
-
final class ClassPart
{
/**
- * @var Collection
+ * @var array
*/
- public readonly Collection $nextPart;
+ public array $nextPart = [];
/**
- * @var Collection
+ * @var ClassValidator[]
*/
- public readonly Collection $validators;
+ public array $validators = [];
public function __construct(public ?string $classGroupId = null)
{
-
- $this->nextPart = new Collection();
- $this->validators = new Collection();
}
public function setClassGroupId(string $classGroupId): static
diff --git a/src/ClassUtils.php b/src/ClassUtils.php
index 353a05c..688599d 100644
--- a/src/ClassUtils.php
+++ b/src/ClassUtils.php
@@ -137,23 +137,26 @@ protected function getGroupRecursive(array $classParts, ClassPart $classPart): ?
}
$currentClassPart = $classParts[0];
- $nextClassPartObject = $classPart->nextPart->get($currentClassPart);
+ $nextClassPartObject = $classPart->nextPart[$currentClassPart] ?? null;
$classGroupFromNextClassPart = $nextClassPartObject ? $this->getGroupRecursive(array_slice($classParts, 1), $nextClassPartObject) : null;
if ($classGroupFromNextClassPart) {
return $classGroupFromNextClassPart;
}
- if ($classPart->validators->isEmpty()) {
+ if (empty($classPart->validators)) {
return null;
}
$classRest = implode('-', $classParts);
- return $classPart
- ->validators
- ->first(fn (ClassValidator $classValidator) => $classValidator->rule->execute($classRest))
- ?->classGroupId ?? null;
+ foreach ($classPart->validators as $classValidator) {
+ if ($classValidator->rule->execute($classRest)) {
+ return $classValidator->classGroupId;
+ }
+ }
+
+ return null;
}
protected function getGroupIdForArbitraryProperty(string $className): ?string
diff --git a/src/Laravel/Facades/TailwindMerge.php b/src/Laravel/Facades/TailwindMerge.php
new file mode 100644
index 0000000..8621a65
--- /dev/null
+++ b/src/Laravel/Facades/TailwindMerge.php
@@ -0,0 +1,18 @@
+app->singleton(TailwindMerge::class, function (): TailwindMerge {
+ return new TailwindMerge(
+ TailwindMergeConfig::fromArray(
+ (array) config('tailwind-merge', []),
+ config('tailwind-merge.strategy', 'merge') === 'merge'
+ )
+ );
+ });
+
+ $this->app->alias(TailwindMerge::class, 'tailwind-merge');
+
+ require_once __DIR__ . '/helpers.php';
+ }
+
public function boot(): void
{
+ if ($this->app->runningInConsole()) {
+ $this->publishes([
+ __DIR__ . '/config/tailwind-merge.php' => config_path('tailwind-merge.php'),
+ ]);
+ }
+
+ ComponentAttributeBag::macro('tw', function (...$args) {
+ /* @phpstan-ignore-next-line */
+ $this->attributes['class'] = app(TailwindMerge::class)->merge(
+ $args,
+ /* @phpstan-ignore-next-line */
+ $this->attributes['class'] ?? ''
+ );
+
+ return $this;
+ });
+
Blade::directive('tw', function ($expression) {
$expression = is_null($expression) ? '()' : $expression;
- return "class=\"\"";
+ return "class=\"\"";
});
}
+
+ public function provides(): array
+ {
+ return [
+ TailwindMerge::class,
+ 'tailwind-merge',
+ ];
+ }
}
diff --git a/src/Laravel/config/tailwind-merge.php b/src/Laravel/config/tailwind-merge.php
new file mode 100644
index 0000000..0648850
--- /dev/null
+++ b/src/Laravel/config/tailwind-merge.php
@@ -0,0 +1,106 @@
+ 'merge',
+
+ /*
+ |--------------------------------------------------------------------------
+ | LRU Cache Size
+ |--------------------------------------------------------------------------
+ |
+ | Integer indicating size of LRU cache used for memoizing results.
+ | Cache might be up to twice as big as `cacheSize`.
+ | To disable cache, set to 0.
+ */
+
+ 'cache_size' => 5000,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Separator
+ |--------------------------------------------------------------------------
+ |
+ | Custom separator for modifiers in Tailwind classes.
+ | See https://tailwindcss.com/docs/configuration#separator
+ */
+
+ 'separator' => ':',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Prefix
+ |--------------------------------------------------------------------------
+ |
+ | Prefix added to Tailwind-generated classes.
+ | See https://tailwindcss.com/docs/configuration#prefix
+ */
+
+ 'prefix' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Theme
+ |--------------------------------------------------------------------------
+ |
+ | Theme scales used in classGroups.
+ | The keys are the same as in the Tailwind config but the values are sometimes defined more broadly.
+ */
+
+ 'theme' => [],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Class Groups
+ |--------------------------------------------------------------------------
+ |
+ | Object with groups of classes.
+ | Example : {
+ | // Creates group of classes `group`, `of` and `classes`
+ | 'group-id': ['group', 'of', 'classes'],
+ | // Creates group of classes `look-at-me-other` and `look-at-me-group`.
+ | 'other-group': [{ 'look-at-me': ['other', 'group']}]
+ | }
+ */
+
+ 'class_groups' => [],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Conflicting Class Groups
+ |--------------------------------------------------------------------------
+ |
+ | Conflicting classes across groups.
+ | The key is ID of class group which creates conflict, values are IDs of class groups which receive a conflict.
+ | A class group is ID is the key of a class group in classGroups object.
+ |
+ | Example : { gap: ['gap-x', 'gap-y'] }
+ */
+
+ 'conflicting_class_groups' => [],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Conflicting Class Group Modifiers
+ |--------------------------------------------------------------------------
+ |
+ | A class group ID is the key of a class group in classGroups object.
+ |
+ | Example : { 'font-size': ['leading'] }
+ */
+
+ 'conflicting_class_group_modifiers' => [],
+
+];
diff --git a/src/Laravel/helpers.php b/src/Laravel/helpers.php
new file mode 100644
index 0000000..cdbe441
--- /dev/null
+++ b/src/Laravel/helpers.php
@@ -0,0 +1,18 @@
+merge(...$classLists);
+ }
+}
diff --git a/src/TailwindMerge.php b/src/TailwindMerge.php
index 4faf317..b4e54a8 100644
--- a/src/TailwindMerge.php
+++ b/src/TailwindMerge.php
@@ -5,22 +5,21 @@
namespace YieldStudio\TailwindMerge;
use Closure;
-use Illuminate\Support\Collection;
use YieldStudio\TailwindMerge\Interfaces\TailwindMergePlugin;
final class TailwindMerge
{
- protected const SPLIT_CLASSES_REGEX = '/\s+/';
+ private const SPLIT_CLASSES_REGEX = '/\s+/';
- protected const IMPORTANT_MODIFIER = '!';
+ private const IMPORTANT_MODIFIER = '!';
- protected LruCache $cache;
+ private LruCache $cache;
- protected ClassUtils $classUtils;
+ private ClassUtils $classUtils;
- protected TailwindMergeConfig $config;
+ private TailwindMergeConfig $config;
- protected static ?TailwindMerge $instance = null;
+ private static ?TailwindMerge $instance = null;
public function __construct(?TailwindMergeConfig $config = null)
{
@@ -62,104 +61,36 @@ public function extend(Closure|TailwindMergePlugin ...$plugins): TailwindMerge
public static function instance(): TailwindMerge
{
- if (! self::$instance) {
+ if (!self::$instance) {
self::$instance = new TailwindMerge();
}
return self::$instance;
}
- protected function mergeClassList(string $classList): string
+ private function mergeClassList(string $classList): string
{
- /**
- * Set of classGroupIds in following format:
- * `{importantModifier}{variantModifiers}{classGroupId}`
- *
- * @example 'float'
- * @example 'hover:focus:bg-color'
- * @example 'md:!pr'
- */
- $classGroupsInConflict = new Collection();
-
- return (new Collection((array) preg_split(self::SPLIT_CLASSES_REGEX, trim($classList))))
- ->map(function ($originalClassName) {
- $modifiersContext = $this->classUtils->splitModifiers((string) $originalClassName);
-
- $classGroupId = $this->classUtils->getClassGroupId(
- is_null($modifiersContext->maybePostfixModifierPosition)
- ? $modifiersContext->baseClassName
- : substr($modifiersContext->baseClassName, 0, $modifiersContext->maybePostfixModifierPosition)
- );
-
- $hasPostfixModifier = is_int($modifiersContext->maybePostfixModifierPosition);
-
- if (! $classGroupId) {
- if (is_null($modifiersContext->maybePostfixModifierPosition)) {
- return [
- 'isTailwindClass' => false,
- 'originalClassName' => $originalClassName,
- ];
- }
-
- $classGroupId = $this->classUtils->getClassGroupId($modifiersContext->baseClassName);
+ $classes = (array)preg_split(self::SPLIT_CLASSES_REGEX, trim($classList));
- if (! $classGroupId) {
- return [
- 'isTailwindClass' => false,
- 'originalClassName' => $originalClassName,
- ];
- }
-
- $hasPostfixModifier = false;
- }
+ $classes = array_map([$this, 'determineClassContext'], $classes);
- $variantModifier = implode(':', $this->classUtils->sortModifiers($modifiersContext->modifiers));
- $modifierId = $modifiersContext->hasImportantModifier
- ? $variantModifier.self::IMPORTANT_MODIFIER
- : $variantModifier;
-
- return [
- 'isTailwindClass' => true,
- 'modifierId' => $modifierId,
- 'classGroupId' => $classGroupId,
- 'originalClassName' => $originalClassName,
- 'hasPostfixModifier' => $hasPostfixModifier,
- ];
- })
- // Last class in conflict wins, so we need to filter conflicting classes in reverse order.
- ->reverse()
- ->filter(function (array $parsed) use ($classGroupsInConflict) {
- if (! $parsed['isTailwindClass']) {
- return true;
- }
+ // Last class in conflict wins, so we need to filter conflicting classes in reverse order.
+ $classes = array_reverse($classes);
- /** @phpstan-ignore-next-line */
- $classId = $parsed['modifierId'].$parsed['classGroupId'];
- if ($classGroupsInConflict->contains($classId)) {
- return false;
- }
+ $classes = $this->filterConflictingClasses($classes);
- $classGroupsInConflict->push($classId);
+ $classes = array_map(fn (ClassContext $context) => $context->originalClassName, $classes);
- /** @phpstan-ignore-next-line */
- $conflicts = $this->classUtils->getConflictingClassGroupIds($parsed['classGroupId'], $parsed['hasPostfixModifier']);
- foreach ($conflicts as $group) {
- /** @phpstan-ignore-next-line */
- $classGroupsInConflict->push($parsed['modifierId'].$group);
- }
+ // Reorder in the good way
+ $classes = array_reverse($classes);
- return true;
- })
- ->reverse()
- ->map(fn (array $parsed) => $parsed['originalClassName'])
- ->join(' ');
+ return implode(' ', $classes);
}
/**
- * @param string|array|array ...$classList
- * @return string
+ * @param string|array ...$classList
*/
- protected function join(string|array ...$classList): string
+ private function join(string|array ...$classList): string
{
$output = [];
@@ -181,4 +112,85 @@ protected function join(string|array ...$classList): string
return implode(' ', $output);
}
+
+ private function determineClassContext(string $originalClassName): ClassContext
+ {
+ $modifiersContext = $this->classUtils->splitModifiers($originalClassName);
+
+ $classGroupId = $this->classUtils->getClassGroupId(
+ is_null($modifiersContext->maybePostfixModifierPosition)
+ ? $modifiersContext->baseClassName
+ : substr($modifiersContext->baseClassName, 0, $modifiersContext->maybePostfixModifierPosition)
+ );
+
+ $hasPostfixModifier = is_int($modifiersContext->maybePostfixModifierPosition);
+
+ if (!$classGroupId) {
+ if (is_null($modifiersContext->maybePostfixModifierPosition)) {
+ return new ClassContext(
+ isTailwindClass: false,
+ originalClassName: $originalClassName
+ );
+ }
+
+ $classGroupId = $this->classUtils->getClassGroupId($modifiersContext->baseClassName);
+
+ if (!$classGroupId) {
+ return new ClassContext(
+ isTailwindClass: false,
+ originalClassName: $originalClassName
+ );
+ }
+
+ $hasPostfixModifier = false;
+ }
+
+ $variantModifier = implode(':', $this->classUtils->sortModifiers($modifiersContext->modifiers));
+ $modifierId = $modifiersContext->hasImportantModifier
+ ? $variantModifier . self::IMPORTANT_MODIFIER
+ : $variantModifier;
+
+ return new ClassContext(
+ isTailwindClass: true,
+ originalClassName: $originalClassName,
+ hasPostfixModifier: $hasPostfixModifier,
+ modifierId: $modifierId,
+ classGroupId: $classGroupId,
+ );
+ }
+
+ private function filterConflictingClasses(array $classes): array
+ {
+ /**
+ * Set of classGroupIds in following format:
+ * `{importantModifier}{variantModifiers}{classGroupId}`
+ *
+ * @example 'float'
+ * @example 'hover:focus:bg-color'
+ * @example 'md:!pr'
+ *
+ * @var string[] $classGroupsInConflict
+ */
+ $classGroupsInConflict = [];
+
+ return array_filter($classes, function (ClassContext $context) use (&$classGroupsInConflict) {
+ if (!$context->isTailwindClass) {
+ return true;
+ }
+
+ $classId = $context->modifierId . $context->classGroupId;
+ if (in_array($classId, $classGroupsInConflict)) {
+ return false;
+ }
+
+ $classGroupsInConflict[] = $classId;
+
+ $conflicts = $this->classUtils->getConflictingClassGroupIds((string) $context->classGroupId, $context->hasPostfixModifier);
+ foreach ($conflicts as $group) {
+ $classGroupsInConflict[] = $context->modifierId . $group;
+ }
+
+ return true;
+ });
+ }
}
diff --git a/src/TailwindMergeConfig.php b/src/TailwindMergeConfig.php
index e1b2a2f..c23fbf2 100644
--- a/src/TailwindMergeConfig.php
+++ b/src/TailwindMergeConfig.php
@@ -27,9 +27,9 @@ final class TailwindMergeConfig
* @param $cacheSize int Integer indicating size of LRU cache used for memoizing results.
* - Cache might be up to twice as big as `cacheSize`
* - No cache is used for values <= 0
- * @param $separator string Custom separator for modifiers in Tailwind classes
+ * @param $separator string Custom separator for modifiers in Tailwind classes.
* See https://tailwindcss.com/docs/configuration#separator
- * @param $prefix null|string Prefix added to Tailwind-generated classes
+ * @param $prefix null|string Prefix added to Tailwind-generated classes.
* See https://tailwindcss.com/docs/configuration#prefix
* @param $theme array Theme scales used in classGroups.
* The keys are the same as in the Tailwind config but the values are sometimes defined more broadly.
@@ -44,7 +44,10 @@ final class TailwindMergeConfig
* The key is ID of class group which creates conflict, values are IDs of class groups which receive a conflict.
* A class group is ID is the key of a class group in classGroups object.
* Example : { gap: ['gap-x', 'gap-y'] }
- *
+ * @param $conflictingClassGroupModifiers array Postfix modifiers conflicting with other class groups.
+ * A class group ID is the key of a class group in classGroups object.
+ * Example : { 'font-size': ['leading'] }
+
* @throws BadThemeException
*/
public function __construct(
@@ -59,6 +62,25 @@ public function __construct(
$this->validate();
}
+
+ /**
+ * @param array $config
+ * @param bool $extend Determine if given values extends or replace the default configuration
+ */
+ public static function fromArray(array $config, bool $extend = true): static
+ {
+ $output = static::default();
+
+ foreach($config as $key => $value) {
+ $methodName = lcfirst(str_replace('_', '', ucwords($key, '_')));
+ if (method_exists($output, $methodName)) {
+ $output->{$methodName}($value, $extend);
+ }
+ }
+
+ return $output;
+ }
+
/**
* @throws BadThemeException
*/
diff --git a/src/helpers.php b/src/helpers.php
deleted file mode 100644
index 0e363b7..0000000
--- a/src/helpers.php
+++ /dev/null
@@ -1,15 +0,0 @@
-|string ...$classLists
- */
- function tw_merge(array|string ...$classLists): string
- {
- return (new TailwindMerge())->merge(...$classLists);
- }
-}
diff --git a/tests/Laravel/LaravelTest.php b/tests/Laravel/LaravelTest.php
new file mode 100644
index 0000000..5a5b486
--- /dev/null
+++ b/tests/Laravel/LaravelTest.php
@@ -0,0 +1,72 @@
+=')) {
+ test('Laravel is only used for Laravel integration')
+ ->expect('Illuminate')
+ ->toOnlyBeUsedIn('YieldStudio\\TailwindMerge\\Laravel');
+}
+
+it('tailwind merge is correctly bind on the container', function () {
+ $instance = app('tailwind-merge');
+
+ expect($instance)->toBeInstanceOf(TailwindMerge::class);
+
+ expect(app('tailwind-merge'))->toBe($instance);
+ expect(app(TailwindMerge::class))->toBe($instance);
+});
+
+it('customize configuration with merge strategy', function () {
+ /* @phpstan-ignore-next-line */
+ app()->bind('config', fn () => new Repository([
+ 'tailwind-merge' => [
+ 'class_groups' => [
+ 'fooKey' => [[
+ 'fooKey' => ['bar', 'baz']]],
+ ],
+ 'strategy' => 'merge',
+ ],
+ ]));
+
+ /* @phpstan-ignore-next-line */
+ expect(app('tailwind-merge')->merge('fooKey-bar fooKey-baz bg-red-100 bg-red-500'))->toBe('fooKey-baz bg-red-500');
+});
+
+it('customize configuration with replace strategy', function () {
+ /* @phpstan-ignore-next-line */
+ app()->bind('config', fn () => new Repository([
+ 'tailwind-merge' => [
+ 'class_groups' => [
+ 'fooKey' => [[
+ 'fooKey' => ['bar', 'baz']]],
+ ],
+ 'strategy' => 'replace',
+ ],
+ ]));
+
+ /* @phpstan-ignore-next-line */
+ expect(app('tailwind-merge')->merge('fooKey-bar fooKey-baz bg-red-100 bg-red-500'))->toBe('fooKey-baz bg-red-100 bg-red-500');
+});
+
+it('tw helper works correctly', function () {
+ expect(tw('text-red-100 text-red-500'))->toBe('text-red-500');
+});
+
+it('blade components attribute works correctly', function () {
+ Blade::component('avatar', Avatar::class);
+
+ $this->blade('')->assertSee('', false);
+});
+
+
+it('blade directive works correctly', function () {
+ $this->blade('')->assertSee('class="text-red-500"', false);
+});
diff --git a/tests/Laravel/TestSupport/Avatar.php b/tests/Laravel/TestSupport/Avatar.php
new file mode 100644
index 0000000..8b31c04
--- /dev/null
+++ b/tests/Laravel/TestSupport/Avatar.php
@@ -0,0 +1,17 @@
+tw('rounded-full') }}>
+ blade;
+ }
+}
diff --git a/tests/LaravelTestCase.php b/tests/LaravelTestCase.php
new file mode 100644
index 0000000..5145395
--- /dev/null
+++ b/tests/LaravelTestCase.php
@@ -0,0 +1,23 @@
+in('Unit');
+
+uses(LaravelTestCase::class)->in('Laravel');
diff --git a/tests/Unit/ClassMapTest.php b/tests/Unit/ClassMapTest.php
index 10fde17..be56ac2 100644
--- a/tests/Unit/ClassMapTest.php
+++ b/tests/Unit/ClassMapTest.php
@@ -2,35 +2,41 @@
declare(strict_types=1);
-use Illuminate\Support\Collection;
use YieldStudio\TailwindMerge\ClassMapFactory;
use YieldStudio\TailwindMerge\ClassPart;
-use YieldStudio\TailwindMerge\ClassValidator;
use YieldStudio\TailwindMerge\TailwindMergeConfig;
function getClassGroupsInClassPart(ClassPart $classPart): array
{
- $classGroups = new Collection();
+ $classGroups = [];
if ($classPart->classGroupId) {
- $classGroups->push($classPart->classGroupId);
+ $classGroups[] = $classPart->classGroupId;
}
- $classPart->validators->each(fn (ClassValidator $validator) => $classGroups->push($validator->classGroupId));
+ foreach ($classPart->validators as $validator) {
+ $classGroups[] = $validator->classGroupId;
+ }
+
+ foreach ($classPart->nextPart as $nextClassPart) {
+ $classGroups = array_merge($classGroups, getClassGroupsInClassPart($nextClassPart));
+ }
- $classPart->nextPart->each(function (ClassPart $nextClassPart) use ($classGroups) {
- $classGroups->push(...getClassGroupsInClassPart($nextClassPart));
- });
+ $classGroups = array_unique($classGroups);
+ sort($classGroups);
- return $classGroups->unique()->sort()->values()->toArray();
+ $classGroups = array_values($classGroups);
+
+ return $classGroups;
}
test('class map has correct class groups at first part', function () {
$classMap = ClassMapFactory::create(TailwindMergeConfig::default());
- $classGroupsByFirstPart = $classMap->nextPart
- ->mapWithKeys(fn ($value, $key) => [$key => getClassGroupsInClassPart($value)])
- ->toArray();
+ $classGroupsByFirstPart = [];
+ foreach ($classMap->nextPart as $key => $classPart) {
+ $classGroupsByFirstPart[$key] = getClassGroupsInClassPart($classPart);
+ }
$this->assertNull($classMap->classGroupId);
$this->assertCount(0, $classMap->validators);
diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php
deleted file mode 100644
index 2c82011..0000000
--- a/tests/Unit/HelpersTest.php
+++ /dev/null
@@ -1,7 +0,0 @@
-toBe('hidden');
-});