ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
MigratedLanguageFileSyncPluralOverlayTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
28use PHPUnit\Framework\TestCase;
29
36{
38
39 private const string MODULE = 'ptest';
40
41 private string $fixture_directory;
42 private string $client_data_dir;
45
46 protected function setUp(): void
47 {
48 // never the build of the installation, see MigratedPoFixture::resetRuntime()
51
52 if (!defined('ILIAS_ABSOLUTE_PATH')) {
53 define('ILIAS_ABSOLUTE_PATH', realpath(__DIR__ . '/../../../../../'));
54 }
55
56 $this->fixture_directory = __DIR__ . '/tmp-plural-sync-fixtures-' . bin2hex(random_bytes(4));
57 mkdir($this->fixture_directory, 0775, true);
58 $this->client_data_dir = sys_get_temp_dir() . '/ilias_mlfs_plural_test_' . bin2hex(random_bytes(4));
59 mkdir($this->client_data_dir, 0775, true);
60
61 $this->directory = MigratedPoFixture::directory(
62 self::MODULE,
63 'components/ILIAS/Language/tests/ComponentTranslation/' . basename($this->fixture_directory) . '/'
64 );
65 $this->manager = new LanguageFileDirectoryManager(new CustomizingLanguageFileDirectory(), $this->directory);
66 }
67
68 protected function tearDown(): void
69 {
70 MigratedPoFixture::removeDirectory($this->fixture_directory);
71 MigratedPoFixture::removeDirectory($this->client_data_dir);
72
74 }
75
76 private function shippedPo(string $lang_key = 'de'): string
77 {
78 return $this->fixture_directory . '/' . self::MODULE . '_' . $lang_key . '.po';
79 }
80
81 private function overlayBase(string $lang_key = 'de'): string
82 {
83 return MigratedPoFixture::overlayBase($this->client_data_dir, $this->directory, $lang_key);
84 }
85
86 private function overlayPo(): TranslationCatalog
87 {
88 return MigratedPoFixture::readPo($this->overlayBase() . '.po');
89 }
90
95 private function seedShipped(array $entries, string $header = 'nplurals=2; plural=(n != 1);'): void
96 {
97 $catalog = new TranslationCatalog();
98 $catalog->setHeader('Content-Type', 'text/plain; charset=UTF-8');
99 $catalog->setHeader('Plural-Forms', $header);
100 foreach ($entries as $identifier => $value) {
101 $entry = new TranslationEntry(self::MODULE, (string) $identifier);
102 if (is_array($value)) {
103 $entry->setPlural($identifier . '_plural', $value);
104 } else {
105 $entry->translate($value);
106 }
107 $catalog->add($entry);
108 }
109 MigratedPoFixture::writePo($this->shippedPo(), $catalog);
110 }
111
116 private function sync(array $entries, bool $refresh = false): void
117 {
118 MigratedLanguageFileSync::sync(
119 $this->manager,
120 ILIAS_ABSOLUTE_PATH,
121 'de',
122 self::MODULE,
123 $entries,
124 $this->client_data_dir,
125 $refresh
126 );
127 }
128
129 private function overlayEntry(string $identifier): ?TranslationEntry
130 {
131 return $this->overlayPo()->find(null, $identifier);
132 }
133
137 private function syncWithLocalChangeDates(array $dates): void
138 {
139 MigratedLanguageFileSync::sync(
140 $this->manager,
141 ILIAS_ABSOLUTE_PATH,
142 'de',
143 self::MODULE,
144 ['item [0]' => 'Eintrag NEU', 'item [1]' => 'Einträge NEU'],
145 $this->client_data_dir,
146 false,
147 null,
148 null,
149 $dates
150 );
151 }
152
159 {
160 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
161
163 'item [0]' => '2021-05-06 07:08:09',
164 'item [1]' => '2020-01-02 03:04:05',
165 'itemx [0]' => '2023-01-01 00:00:00',
166 ]);
167
168 $this->assertSame('2021-05-06T07:08:09Z', LocalChangeComments::getLocalChange($this->overlayEntry('item')));
169 }
170
176 {
177 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
178
179 $this->syncWithLocalChangeDates(['item' => '2019-02-03 04:05:06', 'item [0]' => '2021-05-06 07:08:09']);
180
181 $this->assertSame('2019-02-03T04:05:06Z', LocalChangeComments::getLocalChange($this->overlayEntry('item')));
182 }
183
185 {
186 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
187 $before = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
188
189 $this->syncWithLocalChangeDates(['item [0]' => 'garbage', 'item [1]' => '2021-05-06 07:08:09']);
190 $this->assertSame('2021-05-06T07:08:09Z', LocalChangeComments::getLocalChange($this->overlayEntry('item')));
191
192 $this->tearDownOverlay();
193 $this->syncWithLocalChangeDates(['item [0]' => 'garbage']);
194 $change = LocalChangeComments::getLocalChange($this->overlayEntry('item'));
195 $this->assertNotNull($change);
196 $this->assertGreaterThanOrEqual($before->format('Y-m-d\TH:i:s\Z'), $change);
197 }
198
199 private function tearDownOverlay(): void
200 {
201 MigratedPoFixture::removeDirectory($this->client_data_dir);
202 mkdir($this->client_data_dir, 0775, true);
203 }
204
211 {
212 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
213
214 $this->sync(['item [0]' => 'Eintrag', 'item [1]' => 'Einträge NEU']);
215
216 $entry = $this->overlayEntry('item');
217 $this->assertNotNull($entry, 'the plural message must be in the overlay');
218 $this->assertTrue($entry->isPlural());
219 $this->assertSame(['Eintrag', 'Einträge NEU'], $entry->getPluralTranslations());
220 $this->assertSame('Eintrag', LocalChangeComments::getOriginal($entry, 0));
221 $this->assertSame('Einträge', LocalChangeComments::getOriginal($entry, 1));
222 }
223
229 {
230 $this->seedShipped(['item' => ['Eintrag', 'Einträge'], 'plain' => 'X']);
231 $this->sync(['item [0]' => 'Eintrag', 'item [1]' => 'GEÄNDERT', 'plain' => 'Anders']);
232 $this->assertNotNull($this->overlayEntry('item'), 'precondition: the plural message is in the overlay');
233
234 $this->sync(['item [0]' => 'Eintrag', 'item [1]' => 'Einträge', 'plain' => 'Anders']);
235
236 $this->assertNull($this->overlayEntry('item'));
237 $plain = $this->overlayEntry('plain');
238 $this->assertNotNull($plain, 'the unrelated local change must survive');
239 $this->assertSame('Anders', $plain->getTranslation());
240 }
241
248 {
249 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
250 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => 'FORM1-LOKAL']);
251
252 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => '']);
253
254 $entry = $this->overlayEntry('item');
255 $this->assertNotNull($entry);
256 $this->assertSame(['FORM0-LOKAL', 'Einträge'], $entry->getPluralTranslations());
257 }
258
266 {
267 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
268 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => 'Einträge']);
269 $entry = $this->overlayEntry('item');
270 $this->assertNotNull($entry);
271 $this->assertSame('Einträge', LocalChangeComments::getOriginal($entry, 1), 'precondition');
272
273 // the shipped file changes only form 1
274 $this->seedShipped(['item' => ['Eintrag', 'Einträge NEU']]);
275 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => 'Einträge NEU'], refresh: true);
276
277 $entry = $this->overlayEntry('item');
278 $this->assertNotNull($entry);
279 $this->assertSame(['FORM0-LOKAL', 'Einträge NEU'], $entry->getPluralTranslations());
280 $this->assertSame('Eintrag', LocalChangeComments::getOriginal($entry, 0));
281 $this->assertSame('Einträge NEU', LocalChangeComments::getOriginal($entry, 1));
282 }
283
290 {
291 $this->seedShipped(['item' => ['Eintrag', 'Einträge']]);
292 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => 'Einträge']);
293
294 $this->seedShipped(['item' => ['Eintrag', 'Einträge NEU']]);
295 // form 1's value must be resubmitted as its (new) current value to stay out of the delta -
296 // an ordinary caller always reads currentModuleContent() first, which already reflects it
297 $this->sync(['item [0]' => 'FORM0-LOKAL', 'item [1]' => 'Einträge NEU'], refresh: false);
298
299 $entry = $this->overlayEntry('item');
300 $this->assertNotNull($entry);
301 $this->assertSame('Einträge', LocalChangeComments::getOriginal($entry, 1), 'original stays the OLD shipped value');
302 }
303}
trait RestoresProcessState
Whatever a test does to the process (locale, LANGUAGE/LC_*, LOCPATH, the static state of the runtime ...
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
One message of a TranslationCatalog (a msgctxt/msgid/msgstr block of a .po file).
Tracks per overlay entry whether the current value still matches the value of the shipped ....
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
MigratedLanguageFileSync::sync() for a plural message: what the overlay .po holds when only some form...
testAnEmptyFormValueResetsOnlyThatFormToShippedKeepingTheOtherFormsLocalChange()
Submitting an empty value for one form resets exactly that form to its shipped value - even while the...
testWithoutRefreshTheExistingOriginalIsLeftUntouchedWhenTheShippedValueChanges()
Without $refresh_original_from_shipped (an ordinary form save), the "original" of an existing overlay...
testRefreshOriginalFromShippedUpdatesOriginalOfEveryFormToTheNewShippedValue()
$refresh_original_from_shipped=true (a Setup update) updates every form's "original" to the new shipp...
testADateUnderThePluralIdentifierWinsOverTheFormDates()
A date under the identifier itself wins over the dates of the forms; an unreadable date of a form is ...
seedShipped(array $entries, string $header='nplurals=2;plural=(n !=1);')
testAllFormsBackToShippedRemovesTheEntryButKeepsOtherLocalChanges()
Once every form is back to its shipped value, the whole entry disappears from the overlay - a still-l...
testOnlyOneFormChangedWritesTheWholeEntryWithAnOriginalPerForm()
Only one of two forms is locally changed: the overlay still carries the whole plural entry (both form...
testANewPluralEntryTakesTheLatestLocalChangeDateOfItsForms()
A new plural overlay entry whose dates are known only per form (rows "item [0]", "item [1]" of lng_da...
static directory(string $prefix, string $path, bool $local=false)
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static readPo(string $file)
static overlayBase(string $client_data_dir, LanguageFileDirectory $directory, string $lang_key)
The overlay of $directory's module for $lang_key without the .po extension.
static resetRuntime(?string $artifact_directory=null)
Forgets the static state of the runtime read path and points build and runtime at $artifact_directory...
A directory of language files with a path and a prefix for them.