resolve(['kind' => 'this']); expect($range)->toBeInstanceOf(SalesPeriodRange::class); expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-06-01 00:00:00'); expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-06-30 23:59:59'); }); // ─── kind=prev ──────────────────────────────────────────────────────────────── it('kind=prev returns previous month range (May 2026)', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve(['kind' => 'prev']); expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-05-01 00:00:00'); expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-31 23:59:59'); }); // ─── kind=prev2 ─────────────────────────────────────────────────────────────── it('kind=prev2 returns month-before-previous range (April 2026)', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve(['kind' => 'prev2']); expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-04-01 00:00:00'); expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-04-30 23:59:59'); }); // ─── kind=custom ────────────────────────────────────────────────────────────── it('kind=custom returns exact from/to bounds', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve([ 'kind' => 'custom', 'from' => '2026-03-10', 'to' => '2026-05-20', ]); expect($range->start->format('Y-m-d H:i:s'))->toBe('2026-03-10 00:00:00'); expect($range->end->format('Y-m-d H:i:s'))->toBe('2026-05-20 23:59:59'); }); it('kind=custom: monthsIn returns all three month-starts for Mar-May span', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve([ 'kind' => 'custom', 'from' => '2026-03-10', 'to' => '2026-05-20', ]); $months = $resolver->monthsIn($range); expect($months)->toHaveCount(3); expect($months[0]->format('Y-m-d'))->toBe('2026-03-01'); expect($months[1]->format('Y-m-d'))->toBe('2026-04-01'); expect($months[2]->format('Y-m-d'))->toBe('2026-05-01'); }); it('kind=custom: throws InvalidArgumentException when from > to', function (): void { $resolver = new SalesPeriodResolver; expect(fn () => $resolver->resolve([ 'kind' => 'custom', 'from' => '2026-05-20', 'to' => '2026-03-10', ]))->toThrow(InvalidArgumentException::class); }); it('kind=custom: throws InvalidArgumentException when from is missing', function (): void { $resolver = new SalesPeriodResolver; expect(fn () => $resolver->resolve([ 'kind' => 'custom', 'to' => '2026-05-20', ]))->toThrow(InvalidArgumentException::class); }); it('kind=custom: throws InvalidArgumentException when to is missing', function (): void { $resolver = new SalesPeriodResolver; expect(fn () => $resolver->resolve([ 'kind' => 'custom', 'from' => '2026-03-10', ]))->toThrow(InvalidArgumentException::class); }); // ─── monthsIn ───────────────────────────────────────────────────────────────── it('monthsIn for single-month "this" range returns one entry (June 2026)', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve(['kind' => 'this']); $months = $resolver->monthsIn($range); expect($months)->toHaveCount(1); expect($months[0]->format('Y-m-d'))->toBe('2026-06-01'); }); // ─── timezone ───────────────────────────────────────────────────────────────── it('range start carries Europe/Moscow timezone', function (): void { $resolver = new SalesPeriodResolver; $range = $resolver->resolve(['kind' => 'this']); expect($range->start->timezoneName)->toBe('Europe/Moscow'); });