Skip to content

Commit 7cc0b01

Browse files
committed
Update tests
1 parent fd7da0d commit 7cc0b01

File tree

2 files changed

+236
-21
lines changed

2 files changed

+236
-21
lines changed

tests/Pest.php

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function setupCompilerPaths(CompilerInterface $compiler): void
1313

1414
try {
1515
$reflection->getProperty('nodePath')->setValue($compiler, 'node');
16-
$reflection->getProperty('bridgePath')->setValue($compiler, __DIR__ . '/../../bin/bridge.js');
16+
$reflection->getProperty('bridgePath')->setValue($compiler, __DIR__ . '/../bin/bridge.js');
1717
} catch (ReflectionException) {}
1818
}
1919

@@ -22,7 +22,9 @@ function mockProcess(string $expectedCss, ?array $expectedSourceMap = null): Moc
2222
$mockProcess = Mockery::mock(Process::class);
2323
$mockProcess->shouldReceive('setInput')->andReturnSelf();
2424
$mockProcess->shouldReceive('run')->andReturn(0);
25-
$mockProcess->shouldReceive('getOutput')->andReturn(json_encode(['css' => $expectedCss, 'sourceMap' => $expectedSourceMap]));
25+
$mockProcess->shouldReceive('getOutput')->andReturn(json_encode([
26+
'css' => $expectedCss, 'sourceMap' => $expectedSourceMap
27+
]));
2628
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
2729

2830
return $mockProcess;
@@ -60,7 +62,13 @@ function generateLargeScss(int $size): string
6062
return '$large: "' . $largeVariable . '"; body::after { content: $large; }';
6163
}
6264

63-
function mockProcessForGenerator(string $scss, string $expectedCss, ?array $expectedSourceMap = null, bool $isStreamed = false, array $chunks = []): Process
65+
function mockProcessForGenerator(
66+
string $scss,
67+
string $expectedCss,
68+
?array $expectedSourceMap = null,
69+
bool $isStreamed = false,
70+
array $chunks = []
71+
): Process
6472
{
6573
$mockProcess = Mockery::mock(Process::class);
6674

@@ -80,7 +88,14 @@ function mockProcessForGenerator(string $scss, string $expectedCss, ?array $expe
8088
return $mockProcess;
8189
}
8290

83-
function compileAndAssertGenerator(string $scss, string $expectedResult, array $options = [], ?array $expectedSourceMap = null, bool $isStreamed = false, array $chunks = []): void
91+
function compileAndAssertGenerator(
92+
string $scss,
93+
string $expectedResult,
94+
array $options = [],
95+
?array $expectedSourceMap = null,
96+
bool $isStreamed = false,
97+
array $chunks = []
98+
): void
8499
{
85100
$mockProcess = mockProcessForGenerator($scss, $expectedResult, $expectedSourceMap, $isStreamed, $chunks);
86101

@@ -122,9 +137,36 @@ function assertFindNodeReturnsPath(bool $isWindows): void
122137
$compiler->shouldReceive('isWindows')->andReturn($isWindows);
123138
$compiler->shouldReceive('createProcess')->andReturn($mockProcess);
124139

125-
$node = (function() {
126-
return $this->findNode();
127-
})->call($compiler);
140+
$node = (fn() => $this->findNode())->call($compiler);
128141

129142
expect($node)->toBeString();
130143
}
144+
145+
function mockPersistentProcess(string $expectedCss, bool $withExit = false): Mock|(MockInterface&Process)
146+
{
147+
$mockProcess = Mockery::mock(Process::class);
148+
$mockProcess->shouldReceive('start')->once();
149+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
150+
$mockProcess->shouldReceive('setInput')->once()->andReturnSelf();
151+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
152+
$mockProcess->shouldReceive('getOutput')->once()->andReturn(json_encode(['css' => $expectedCss]) . "\n");
153+
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
154+
155+
if ($withExit) {
156+
$mockProcess->shouldReceive('setInput')->once()->with(json_encode(['exit' => true]) . "\n")->andReturnSelf();
157+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
158+
$mockProcess->shouldReceive('stop')->once();
159+
}
160+
161+
return $mockProcess;
162+
}
163+
164+
function mockPersistentCompiler(Mock|(MockInterface&Process) $mockProcess): Mock|(MockInterface&Compiler)
165+
{
166+
$mockCompiler = mockCompiler();
167+
$mockCompiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
168+
$mockCompiler->shouldReceive('checkEnvironment')->andReturnNull();
169+
$mockCompiler->shouldReceive('findNode')->andReturn('node');
170+
171+
return $mockCompiler;
172+
}

tests/Unit/CompilerTest.php

Lines changed: 187 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
beforeEach(function () {
99
$this->compiler = new Compiler();
1010

11-
$this->reflection = new ReflectionClass(Compiler::class);
12-
$this->reflection->setStaticPropertyValue('cachedProcess', null);
13-
$this->reflection->setStaticPropertyValue('cachedCommand', null);
11+
$reflection = new ReflectionClass(Compiler::class);
12+
$reflection->setStaticPropertyValue('cachedProcess', null);
13+
$reflection->setStaticPropertyValue('cachedCommand', null);
1414
});
1515

1616
it('compiles a simple SCSS string', function () {
@@ -160,7 +160,7 @@
160160
$mockProcess2->shouldReceive('isRunning')->andReturn(true);
161161

162162
$mockCompiler->shouldReceive('readFile')->with($inputFile)->andReturn($scss1, $scss2);
163-
$mockCompiler->shouldReceive('createProcess')->andReturn($mockProcess1, $mockProcess2);
163+
$mockCompiler->shouldReceive('getOrCreateProcess')->andReturn($mockProcess1, $mockProcess2);
164164
$mockCompiler->shouldReceive('checkEnvironment')->andReturnNull();
165165
$mockCompiler->shouldReceive('findNode')->andReturn('node');
166166

@@ -179,9 +179,6 @@
179179
file_put_contents($inputFile, $scss2);
180180
touch($inputFile, 1001);
181181

182-
$this->reflection->setStaticPropertyValue('cachedProcess', null);
183-
$this->reflection->setStaticPropertyValue('cachedCommand', null);
184-
185182
$changed = $mockCompiler->compileFileAndSave($inputFile, $outputFile);
186183
expect($changed)->toBeTrue();
187184

@@ -523,15 +520,10 @@
523520
$compiler = mockCompiler();
524521
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
525522
$compiler->shouldReceive('findNode')->andReturn('node');
526-
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
527-
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
528-
529-
$this->reflection->setStaticPropertyValue('cachedProcess', $mockProcess);
530-
$this->reflection->setStaticPropertyValue('cachedCommand', ['node', 'bridge.js', '--stdin']);
523+
$compiler->shouldReceive('getOrCreateProcess')->andReturn($mockProcess);
531524

532525
$result1 = $compiler->compileString($scss);
533-
expect($result1)->toBe($expectedCss)
534-
->and($this->reflection->getStaticPropertyValue('cachedProcess'))->toBe($mockProcess);
526+
expect($result1)->toBe($expectedCss);
535527

536528
$result2 = $compiler->compileString($scss);
537529
expect($result2)->toBe($expectedCss);
@@ -562,3 +554,184 @@
562554
expect($css)->toBe($expectedCss . "\n/*# sourceMappingURL=output.map */")
563555
->and(file_exists($expectedMapFile))->toBeTrue();
564556
});
557+
558+
it('compiles multiple requests using persistent mode', function () {
559+
$scss1 = '$color: red; body { color: $color; }';
560+
$scss2 = '$color: blue; .test { color: $color; }';
561+
$expectedCss1 = 'body{color:red}';
562+
$expectedCss2 = '.test{color:blue}';
563+
564+
$mockProcess = Mockery::mock(Process::class);
565+
$mockProcess->shouldReceive('start')->once();
566+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
567+
$mockProcess->shouldReceive('setInput')->twice()->andReturnSelf();
568+
$mockProcess->shouldReceive('run')->twice()->andReturn(0);
569+
$mockProcess->shouldReceive('getOutput')->andReturn(
570+
json_encode(['css' => $expectedCss1]) . "\n",
571+
json_encode(['css' => $expectedCss2]) . "\n"
572+
);
573+
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
574+
575+
$compiler = mockCompiler();
576+
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
577+
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
578+
$compiler->shouldReceive('findNode')->andReturn('node');
579+
580+
$compiler->enablePersistentMode();
581+
582+
$result1 = $compiler->compileInPersistentMode($scss1);
583+
$result2 = $compiler->compileInPersistentMode($scss2);
584+
585+
expect($result1)->toBe($expectedCss1)
586+
->and($result2)->toBe($expectedCss2);
587+
});
588+
589+
it('compiles in persistent mode with options', function () {
590+
$scss = '$color: green; .box { color: $color; }';
591+
$expectedCss = '.box{color:green}';
592+
593+
$mockProcess = mockPersistentProcess($expectedCss);
594+
$compiler = mockPersistentCompiler($mockProcess);
595+
$compiler->enablePersistentMode();
596+
597+
$result = $compiler->compileInPersistentMode($scss, ['compressed' => true]);
598+
expect($result)->toBe($expectedCss);
599+
});
600+
601+
it('exits persistent mode properly', function () {
602+
$scss = '$color: red; body { color: $color; }';
603+
$expectedCss = 'body{color:red}';
604+
605+
$mockProcess = mockPersistentProcess($expectedCss, true);
606+
$compiler = mockPersistentCompiler($mockProcess);
607+
$compiler->enablePersistentMode();
608+
609+
$result = $compiler->compileInPersistentMode($scss);
610+
expect($result)->toBe($expectedCss);
611+
612+
$compiler->exitPersistentMode();
613+
});
614+
615+
it('throws exception on error in persistent mode', function () {
616+
$scss = '$color: red; invalid syntax }';
617+
$errorMessage = 'Sass parsing error: Invalid syntax';
618+
619+
$mockProcess = Mockery::mock(Process::class);
620+
$mockProcess->shouldReceive('start')->once();
621+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
622+
$mockProcess->shouldReceive('setInput')->once()->andReturnSelf();
623+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
624+
$mockProcess->shouldReceive('getOutput')->once()->andReturn(
625+
json_encode(['error' => $errorMessage]) . "\n"
626+
);
627+
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
628+
629+
$compiler = mockCompiler();
630+
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
631+
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
632+
$compiler->shouldReceive('findNode')->andReturn('node');
633+
$compiler->enablePersistentMode();
634+
635+
expect(fn() => $compiler->compileInPersistentMode($scss))
636+
->toThrow(Exception::class, $errorMessage);
637+
});
638+
639+
it('returns empty css for empty string in persistent mode', function () {
640+
$compiler = new Compiler();
641+
$compiler->enablePersistentMode();
642+
643+
$result = $compiler->compileInPersistentMode('');
644+
expect($result)->toBe('');
645+
});
646+
647+
it('compiles in persistent mode with sourceMap', function () {
648+
$scss = '$color: purple; .box { color: $color; }';
649+
$expectedCss = '.box{color:purple}';
650+
$expectedSourceMap = ['version' => 3, 'mappings' => '...'];
651+
652+
$mockProcess = Mockery::mock(Process::class);
653+
$mockProcess->shouldReceive('start')->once();
654+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
655+
$mockProcess->shouldReceive('setInput')->once()->andReturnSelf();
656+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
657+
$mockProcess->shouldReceive('getOutput')->once()->andReturn(
658+
json_encode(['css' => $expectedCss, 'sourceMap' => $expectedSourceMap]) . "\n"
659+
);
660+
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
661+
662+
$compiler = mockCompiler();
663+
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
664+
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
665+
$compiler->shouldReceive('findNode')->andReturn('node');
666+
$compiler->shouldReceive('processSourceMap')->once()->with($expectedSourceMap, [])->andReturn("\n/*# sourceMappingURL=data:application/json;base64,encoded */");
667+
$compiler->enablePersistentMode();
668+
669+
$result = $compiler->compileInPersistentMode($scss);
670+
expect($result)->toBe($expectedCss . "\n/*# sourceMappingURL=data:application/json;base64,encoded */");
671+
});
672+
673+
it('throws exception when persistent process fails with error output', function () {
674+
$scss = '$color: red; body { color: $color; }';
675+
676+
$mockProcess = Mockery::mock(Process::class);
677+
$mockProcess->shouldReceive('start')->once();
678+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
679+
$mockProcess->shouldReceive('setInput')->once()->andReturnSelf();
680+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
681+
$mockProcess->shouldReceive('getOutput')->once()->andReturn('');
682+
$mockProcess->shouldReceive('getErrorOutput')->once()->andReturn('Compilation failed: syntax error');
683+
684+
$compiler = mockCompiler();
685+
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
686+
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
687+
$compiler->shouldReceive('findNode')->andReturn('node');
688+
$compiler->enablePersistentMode();
689+
690+
expect(fn() => $compiler->compileInPersistentMode($scss))
691+
->toThrow(Exception::class, 'Sass persistent process failed: Compilation failed: syntax error');
692+
});
693+
694+
it('throws exception when persistent process returns invalid json', function () {
695+
$scss = '$color: red; body { color: $color; }';
696+
697+
$mockProcess = Mockery::mock(Process::class);
698+
$mockProcess->shouldReceive('start')->once();
699+
$mockProcess->shouldReceive('isRunning')->andReturn(true);
700+
$mockProcess->shouldReceive('setInput')->once()->andReturnSelf();
701+
$mockProcess->shouldReceive('run')->once()->andReturn(0);
702+
$mockProcess->shouldReceive('getOutput')->once()->andReturn('invalid json');
703+
$mockProcess->shouldReceive('getErrorOutput')->andReturn('');
704+
705+
$compiler = mockCompiler();
706+
$compiler->shouldReceive('createProcess')->once()->andReturn($mockProcess);
707+
$compiler->shouldReceive('checkEnvironment')->andReturnNull();
708+
$compiler->shouldReceive('findNode')->andReturn('node');
709+
$compiler->enablePersistentMode();
710+
711+
expect(fn() => $compiler->compileInPersistentMode($scss))
712+
->toThrow(Exception::class, 'Invalid response from sass persistent bridge');
713+
});
714+
715+
it('resets cached process and command when process is not running', function () {
716+
$cmd = ['node', '--version'];
717+
718+
$mockProcess = Mockery::mock(Process::class);
719+
$mockProcess->shouldReceive('isRunning')->andReturn(false);
720+
721+
$reflection = new ReflectionClass(Compiler::class);
722+
$reflection->setStaticPropertyValue('cachedProcess', $mockProcess);
723+
$reflection->setStaticPropertyValue('cachedCommand', $cmd);
724+
725+
$newMockProcess = Mockery::mock(Process::class);
726+
$newMockProcess->shouldReceive('isRunning')->andReturn(true);
727+
728+
$compiler = Mockery::mock(Compiler::class)->makePartial();
729+
$compiler->shouldAllowMockingProtectedMethods();
730+
$compiler->shouldReceive('createProcess')->once()->with($cmd)->andReturn($newMockProcess);
731+
732+
$result = $compiler->mockery_callSubjectMethod('getOrCreateProcess', [$cmd]);
733+
734+
expect($result)->toBe($newMockProcess)
735+
->and($reflection->getStaticPropertyValue('cachedProcess'))->toBe($newMockProcess)
736+
->and($reflection->getStaticPropertyValue('cachedCommand'))->toBe($cmd);
737+
});

0 commit comments

Comments
 (0)