1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Intl\Tests\Data\Bundle\Reader;
13
14use PHPUnit\Framework\MockObject\MockObject;
15use PHPUnit\Framework\TestCase;
16use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReader;
17use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
18
19/**
20 * @author Bernhard Schussek <bschussek@gmail.com>
21 */
22class BundleEntryReaderTest extends TestCase
23{
24    const RES_DIR = '/res/dir';
25
26    /**
27     * @var BundleEntryReader
28     */
29    private $reader;
30
31    /**
32     * @var MockObject
33     */
34    private $readerImpl;
35
36    private static $data = [
37        'Entries' => [
38            'Foo' => 'Bar',
39            'Bar' => 'Baz',
40        ],
41        'Foo' => 'Bar',
42        'Version' => '2.0',
43    ];
44
45    private static $fallbackData = [
46        'Entries' => [
47            'Foo' => 'Foo',
48            'Bam' => 'Lah',
49        ],
50        'Baz' => 'Foo',
51        'Version' => '1.0',
52    ];
53
54    private static $mergedData = [
55        // no recursive merging -> too complicated
56        'Entries' => [
57            'Foo' => 'Bar',
58            'Bar' => 'Baz',
59        ],
60        'Baz' => 'Foo',
61        'Version' => '2.0',
62        'Foo' => 'Bar',
63    ];
64
65    protected function setUp()
66    {
67        $this->readerImpl = $this->getMockBuilder('Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface')->getMock();
68        $this->reader = new BundleEntryReader($this->readerImpl);
69    }
70
71    public function testForwardCallToRead()
72    {
73        $this->readerImpl->expects($this->once())
74            ->method('read')
75            ->with(self::RES_DIR, 'root')
76            ->willReturn(self::$data);
77
78        $this->assertSame(self::$data, $this->reader->read(self::RES_DIR, 'root'));
79    }
80
81    public function testReadEntireDataFileIfNoIndicesGiven()
82    {
83        $this->readerImpl->expects($this->exactly(2))
84            ->method('read')
85            ->withConsecutive(
86                [self::RES_DIR, 'en'],
87                [self::RES_DIR, 'root']
88            )
89            ->willReturnOnConsecutiveCalls(self::$data, self::$fallbackData);
90
91        $this->assertSame(self::$mergedData, $this->reader->readEntry(self::RES_DIR, 'en', []));
92    }
93
94    public function testReadExistingEntry()
95    {
96        $this->readerImpl->expects($this->once())
97            ->method('read')
98            ->with(self::RES_DIR, 'root')
99            ->willReturn(self::$data);
100
101        $this->assertSame('Bar', $this->reader->readEntry(self::RES_DIR, 'root', ['Entries', 'Foo']));
102    }
103
104    public function testReadNonExistingEntry()
105    {
106        $this->expectException('Symfony\Component\Intl\Exception\MissingResourceException');
107        $this->readerImpl->expects($this->once())
108            ->method('read')
109            ->with(self::RES_DIR, 'root')
110            ->willReturn(self::$data);
111
112        $this->reader->readEntry(self::RES_DIR, 'root', ['Entries', 'NonExisting']);
113    }
114
115    public function testFallbackIfEntryDoesNotExist()
116    {
117        $this->readerImpl->expects($this->exactly(2))
118            ->method('read')
119            ->withConsecutive(
120                [self::RES_DIR, 'en_GB'],
121                [self::RES_DIR, 'en']
122            )
123            ->willReturnOnConsecutiveCalls(self::$data, self::$fallbackData);
124
125        $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Entries', 'Bam']));
126    }
127
128    public function testDontFallbackIfEntryDoesNotExistAndFallbackDisabled()
129    {
130        $this->expectException('Symfony\Component\Intl\Exception\MissingResourceException');
131        $this->readerImpl->expects($this->once())
132            ->method('read')
133            ->with(self::RES_DIR, 'en_GB')
134            ->willReturn(self::$data);
135
136        $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Entries', 'Bam'], false);
137    }
138
139    public function testFallbackIfLocaleDoesNotExist()
140    {
141        $this->readerImpl->expects($this->exactly(2))
142            ->method('read')
143            ->withConsecutive(
144                [self::RES_DIR, 'en_GB'],
145                [self::RES_DIR, 'en']
146            )
147            ->willReturnOnConsecutiveCalls(
148                $this->throwException(new ResourceBundleNotFoundException()),
149                self::$fallbackData
150            );
151
152        $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Entries', 'Bam']));
153    }
154
155    public function testDontFallbackIfLocaleDoesNotExistAndFallbackDisabled()
156    {
157        $this->expectException('Symfony\Component\Intl\Exception\MissingResourceException');
158        $this->readerImpl->expects($this->once())
159            ->method('read')
160            ->with(self::RES_DIR, 'en_GB')
161            ->willThrowException(new ResourceBundleNotFoundException());
162
163        $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Entries', 'Bam'], false);
164    }
165
166    public function provideMergeableValues()
167    {
168        return [
169            ['foo', null, 'foo'],
170            [null, 'foo', 'foo'],
171            [['foo', 'bar'], null, ['foo', 'bar']],
172            [['foo', 'bar'], [], ['foo', 'bar']],
173            [null, ['baz'], ['baz']],
174            [[], ['baz'], ['baz']],
175            [['foo', 'bar'], ['baz'], ['baz', 'foo', 'bar']],
176        ];
177    }
178
179    /**
180     * @dataProvider provideMergeableValues
181     */
182    public function testMergeDataWithFallbackData($childData, $parentData, $result)
183    {
184        if (null === $childData || \is_array($childData)) {
185            $this->readerImpl->expects($this->exactly(2))
186                ->method('read')
187                ->withConsecutive(
188                    [self::RES_DIR, 'en'],
189                    [self::RES_DIR, 'root']
190                )
191                ->willReturnOnConsecutiveCalls($childData, $parentData);
192        } else {
193            $this->readerImpl->expects($this->once())
194                ->method('read')
195                ->with(self::RES_DIR, 'en')
196                ->willReturn($childData);
197        }
198
199        $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', [], true));
200    }
201
202    /**
203     * @dataProvider provideMergeableValues
204     */
205    public function testDontMergeDataIfFallbackDisabled($childData, $parentData, $result)
206    {
207        $this->readerImpl->expects($this->once())
208            ->method('read')
209            ->with(self::RES_DIR, 'en_GB')
210            ->willReturn($childData);
211
212        $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', [], false));
213    }
214
215    /**
216     * @dataProvider provideMergeableValues
217     */
218    public function testMergeExistingEntryWithExistingFallbackEntry($childData, $parentData, $result)
219    {
220        if (null === $childData || \is_array($childData)) {
221            $this->readerImpl->expects($this->exactly(2))
222                ->method('read')
223                ->withConsecutive(
224                    [self::RES_DIR, 'en'],
225                    [self::RES_DIR, 'root']
226                )
227                ->willReturnOnConsecutiveCalls(
228                    ['Foo' => ['Bar' => $childData]],
229                    ['Foo' => ['Bar' => $parentData]]
230                );
231        } else {
232            $this->readerImpl->expects($this->once())
233                ->method('read')
234                ->with(self::RES_DIR, 'en')
235                ->willReturn(['Foo' => ['Bar' => $childData]]);
236        }
237
238        $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', ['Foo', 'Bar'], true));
239    }
240
241    /**
242     * @dataProvider provideMergeableValues
243     */
244    public function testMergeNonExistingEntryWithExistingFallbackEntry($childData, $parentData, $result)
245    {
246        $this->readerImpl
247            ->method('read')
248            ->withConsecutive(
249                [self::RES_DIR, 'en_GB'],
250                [self::RES_DIR, 'en']
251            )
252            ->willReturnOnConsecutiveCalls(['Foo' => 'Baz'], ['Foo' => ['Bar' => $parentData]]);
253
254        $this->assertSame($parentData, $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Foo', 'Bar'], true));
255    }
256
257    /**
258     * @dataProvider provideMergeableValues
259     */
260    public function testMergeExistingEntryWithNonExistingFallbackEntry($childData, $parentData, $result)
261    {
262        if (null === $childData || \is_array($childData)) {
263            $this->readerImpl
264                ->method('read')
265                ->withConsecutive(
266                    [self::RES_DIR, 'en_GB'],
267                    [self::RES_DIR, 'en']
268                )
269                ->willReturnOnConsecutiveCalls(['Foo' => ['Bar' => $childData]], ['Foo' => 'Bar']);
270        } else {
271            $this->readerImpl->expects($this->once())
272                ->method('read')
273                ->with(self::RES_DIR, 'en_GB')
274                ->willReturn(['Foo' => ['Bar' => $childData]]);
275        }
276
277        $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Foo', 'Bar'], true));
278    }
279
280    public function testFailIfEntryFoundNeitherInParentNorChild()
281    {
282        $this->expectException('Symfony\Component\Intl\Exception\MissingResourceException');
283        $this->readerImpl
284            ->method('read')
285            ->withConsecutive(
286                [self::RES_DIR, 'en_GB'],
287                [self::RES_DIR, 'en']
288            )
289            ->willReturnOnConsecutiveCalls(['Foo' => 'Baz'], ['Foo' => 'Bar']);
290
291        $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Foo', 'Bar'], true);
292    }
293
294    /**
295     * @dataProvider provideMergeableValues
296     */
297    public function testMergeTraversables($childData, $parentData, $result)
298    {
299        $parentData = \is_array($parentData) ? new \ArrayObject($parentData) : $parentData;
300        $childData = \is_array($childData) ? new \ArrayObject($childData) : $childData;
301
302        if (null === $childData || $childData instanceof \ArrayObject) {
303            $this->readerImpl
304                ->method('read')
305                ->withConsecutive(
306                    [self::RES_DIR, 'en_GB'],
307                    [self::RES_DIR, 'en']
308                )
309                ->willReturnOnConsecutiveCalls(['Foo' => ['Bar' => $childData]], ['Foo' => ['Bar' => $parentData]]);
310        } else {
311            $this->readerImpl->expects($this->once())
312                ->method('read')
313                ->with(self::RES_DIR, 'en_GB')
314                ->willReturn(['Foo' => ['Bar' => $childData]]);
315        }
316
317        $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', ['Foo', 'Bar'], true));
318    }
319
320    /**
321     * @dataProvider provideMergeableValues
322     */
323    public function testFollowLocaleAliases($childData, $parentData, $result)
324    {
325        $this->reader->setLocaleAliases(['mo' => 'ro_MD']);
326
327        if (null === $childData || \is_array($childData)) {
328            $this->readerImpl
329                ->method('read')
330                ->withConsecutive(
331                    [self::RES_DIR, 'ro_MD'],
332                    // Read fallback locale of aliased locale ("ro_MD" -> "ro")
333                    [self::RES_DIR, 'ro']
334                )
335                ->willReturnOnConsecutiveCalls(['Foo' => ['Bar' => $childData]], ['Foo' => ['Bar' => $parentData]]);
336        } else {
337            $this->readerImpl->expects($this->once())
338                ->method('read')
339                ->with(self::RES_DIR, 'ro_MD')
340                ->willReturn(['Foo' => ['Bar' => $childData]]);
341        }
342
343        $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'mo', ['Foo', 'Bar'], true));
344    }
345}
346