Skip to content

Commit 0900864

Browse files
committed
add test cases
1 parent fc52822 commit 0900864

2 files changed

Lines changed: 210 additions & 2 deletions

File tree

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"keywords": ["php", "json", "stream", "streaming", "progressive json", "progressive-json", "react", "suspense", "laravel", "symfony", "vue", "breadth-first json"],
66
"type": "library",
77
"require": {
8-
"symfony/http-foundation": "^7.3",
8+
"symfony/http-foundation": "^5.4",
99
"php": ">=8.0"
1010
},
1111
"license": "MIT",
@@ -21,7 +21,7 @@
2121
}
2222
],
2323
"require-dev": {
24-
"phpunit/phpunit": "^12.2"
24+
"phpunit/phpunit": "^9.5|^10.0|^11.0"
2525
},
2626
"scripts": {
2727
"test": "phpunit",

tests/ProgressiveJsonStreamerTest.php

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,212 @@ public function testFluentInterface()
240240

241241
$this->assertInstanceOf(ProgressiveJsonStreamer::class, $result);
242242
}
243+
244+
public function testActualStreamingBehavior()
245+
{
246+
$streamer = new ProgressiveJsonStreamer();
247+
$streamer->data([
248+
'immediate' => '{$}',
249+
'delayed' => '{$}',
250+
]);
251+
252+
$executionOrder = [];
253+
254+
$streamer->addPlaceholder('immediate', function () use (&$executionOrder) {
255+
$executionOrder[] = 'immediate_start';
256+
// Simulate some work
257+
usleep(10000); // 10ms
258+
$executionOrder[] = 'immediate_end';
259+
return 'immediate_value';
260+
});
261+
262+
$streamer->addPlaceholder('delayed', function () use (&$executionOrder) {
263+
$executionOrder[] = 'delayed_start';
264+
// Simulate expensive operation
265+
usleep(20000); // 20ms
266+
$executionOrder[] = 'delayed_end';
267+
return 'delayed_value';
268+
});
269+
270+
$streamChunks = [];
271+
$chunkTimes = [];
272+
273+
foreach ($streamer->stream() as $chunk) {
274+
$streamChunks[] = $chunk;
275+
$chunkTimes[] = microtime(true);
276+
}
277+
278+
// Verify streaming behavior
279+
$this->assertCount(3, $streamChunks);
280+
$this->assertCount(3, $chunkTimes);
281+
282+
// Verify execution order (lazy evaluation)
283+
$this->assertEquals([
284+
'immediate_start',
285+
'immediate_end',
286+
'delayed_start',
287+
'delayed_end'
288+
], $executionOrder);
289+
290+
// Verify that chunks are delivered with time gaps
291+
$this->assertGreaterThan($chunkTimes[0], $chunkTimes[1]);
292+
$this->assertGreaterThan($chunkTimes[1], $chunkTimes[2]);
293+
} public function testStreamedResponseOutput()
294+
{
295+
$streamer = new ProgressiveJsonStreamer();
296+
$streamer->data(['message' => '{$}']);
297+
$streamer->addPlaceholder('message', fn() => 'Hello Streaming');
298+
299+
$response = $streamer->asResponse();
300+
301+
// Test that the response is a StreamedResponse
302+
$this->assertInstanceOf(\Symfony\Component\HttpFoundation\StreamedResponse::class, $response);
303+
$this->assertEquals(200, $response->getStatusCode());
304+
305+
// Test headers - use get() method which should exist in all versions
306+
$contentType = $response->headers->get('Content-Type');
307+
$this->assertEquals('application/x-json-stream', $contentType);
308+
309+
$cacheControl = $response->headers->get('Cache-Control');
310+
$this->assertStringContainsString('no-cache', $cacheControl);
311+
} public function testSendMethodWithOutputBuffering()
312+
{
313+
$streamer = new ProgressiveJsonStreamer();
314+
$streamer->data([
315+
'step1' => '{$}',
316+
'step2' => '{$}',
317+
]);
318+
319+
$streamer->addPlaceholder('step1', fn() => 'first');
320+
$streamer->addPlaceholder('step2', fn() => 'second');
321+
322+
// Instead of testing send() directly (which manipulates buffers),
323+
// test that the stream() method works and generates the expected output
324+
$chunks = [];
325+
foreach ($streamer->stream() as $chunk) {
326+
$chunks[] = $chunk;
327+
}
328+
329+
// Verify we get the expected number of chunks
330+
$this->assertCount(3, $chunks); // Initial structure + 2 placeholders
331+
332+
// Verify the content structure
333+
$this->assertStringContainsString('$step1', $chunks[0]);
334+
$this->assertStringContainsString('$step2', $chunks[0]);
335+
$this->assertStringContainsString('first', $chunks[1]);
336+
$this->assertStringContainsString('second', $chunks[2]);
337+
$this->assertStringContainsString('/* $step1 */', $chunks[1]);
338+
$this->assertStringContainsString('/* $step2 */', $chunks[2]);
339+
}
340+
341+
public function testSendMethodHeaders()
342+
{
343+
$streamer = new ProgressiveJsonStreamer();
344+
$streamer->data(['test' => '{$}']);
345+
$streamer->addPlaceholder('test', fn() => 'value');
346+
347+
// Test that the headers method works correctly (used by send())
348+
$reflection = new \ReflectionClass($streamer);
349+
$method = $reflection->getMethod('getStreamingHeaders');
350+
$method->setAccessible(true);
351+
352+
$headers = $method->invoke($streamer);
353+
354+
// Verify streaming headers are properly configured
355+
$this->assertArrayHasKey('Content-Type', $headers);
356+
$this->assertEquals('application/x-json-stream', $headers['Content-Type']);
357+
$this->assertArrayHasKey('Cache-Control', $headers);
358+
$this->assertStringContainsString('no-cache', $headers['Cache-Control']);
359+
$this->assertArrayHasKey('Connection', $headers);
360+
$this->assertEquals('keep-alive', $headers['Connection']);
361+
}
362+
363+
public function testProgressiveDataAvailability()
364+
{
365+
$streamer = new ProgressiveJsonStreamer();
366+
$streamer->data([
367+
'user' => '{$}',
368+
'posts' => '{$}',
369+
'comments' => '{$}',
370+
]);
371+
372+
$dataAvailability = [];
373+
374+
$streamer->addPlaceholder('user', function () use (&$dataAvailability) {
375+
$dataAvailability[] = 'user_resolved';
376+
return ['id' => 1, 'name' => 'John'];
377+
});
378+
379+
$streamer->addPlaceholder('posts', function () use (&$dataAvailability) {
380+
$dataAvailability[] = 'posts_resolved';
381+
return [['title' => 'Post 1'], ['title' => 'Post 2']];
382+
});
383+
384+
$streamer->addPlaceholder('comments', function () use (&$dataAvailability) {
385+
$dataAvailability[] = 'comments_resolved';
386+
return [['text' => 'Comment 1']];
387+
});
388+
389+
$chunks = [];
390+
foreach ($streamer->stream() as $chunk) {
391+
$chunks[] = $chunk;
392+
}
393+
394+
// Verify data is resolved in order and progressively
395+
$this->assertEquals([
396+
'user_resolved',
397+
'posts_resolved',
398+
'comments_resolved'
399+
], $dataAvailability);
400+
401+
// Initial structure should be available immediately
402+
$this->assertStringContainsString('$user', $chunks[0]);
403+
$this->assertStringContainsString('$posts', $chunks[0]);
404+
$this->assertStringContainsString('$comments', $chunks[0]);
405+
406+
// Then individual data chunks
407+
$this->assertStringContainsString('John', $chunks[1]);
408+
$this->assertStringContainsString('Post 1', $chunks[2]);
409+
$this->assertStringContainsString('Comment 1', $chunks[3]);
410+
}
411+
412+
public function testStreamingWithRealTimeConstraints()
413+
{
414+
$streamer = new ProgressiveJsonStreamer();
415+
$streamer->data([
416+
'fast' => '{$}',
417+
'slow' => '{$}',
418+
]);
419+
420+
$timestamps = [];
421+
422+
$streamer->addPlaceholder('fast', function () use (&$timestamps) {
423+
$timestamps['fast'] = microtime(true);
424+
return 'fast_data';
425+
});
426+
427+
$streamer->addPlaceholder('slow', function () use (&$timestamps) {
428+
usleep(50000); // 50ms delay
429+
$timestamps['slow'] = microtime(true);
430+
return 'slow_data';
431+
});
432+
433+
$streamStart = microtime(true);
434+
$chunks = [];
435+
436+
foreach ($streamer->stream() as $chunk) {
437+
$chunks[] = $chunk;
438+
}
439+
440+
$streamEnd = microtime(true);
441+
442+
// Verify that the slow placeholder actually caused a delay
443+
$this->assertGreaterThan($streamStart + 0.04, $streamEnd); // At least 40ms
444+
445+
// Verify fast was resolved before slow
446+
$this->assertLessThan($timestamps['slow'], $timestamps['fast']);
447+
448+
// Verify we got the expected chunks
449+
$this->assertCount(3, $chunks); // Initial + fast + slow
450+
}
243451
}

0 commit comments

Comments
 (0)