- 버전 이력 변경량(+N/-N 줄) 계산이 변경 영역 (줄 수)² 크기의 LCS 표를 만들어 큰 공통 레이아웃의 첫 편집기 저장이 PHP 기본 메모리 한도(128M) 서버에서 500 으로 끝났다. 두 행 DP 로 길이만 구하도록 바꿔 메모리가 줄 수에 비례하고 표시 숫자는 종전과 같다(참조 구현 동치·메모리 상한 회귀 테스트) - 확장 편집 모드 저장이 overlay 확장의 injections 를 비우던 결함: 서버가 주입 노드 출처 메타에 injection 순번을 싣고, 편집기는 그 순번(없으면 원본 노드 id) 으로 되돌리며, 되돌릴 수 없는 노드가 있으면 저장하지 않고 안내한다 - 서빙 캐시 키를 서버 현재 확장 캐시 버전으로만 조립해 저장·복원 두 번째부터 재로드·「최신 불러오기」가 옛 내용을 받던 결함 수정(요청 v 는 HTTP 캐시 우회용) - 409 충돌 안내가 errors 아래의 버전을 읽지 못해 「최신 버전: -1」 로 표시되던 결함을 세 저장 경로 공용 판독으로 수정 - 버전 저장 시 저장자를 기록하고, 복원 시에도 잠금 번호를 올린다 - 회귀 테스트(PHPUnit·Vitest), 트러블슈팅 사례 32~34, 규정·API 문서, ja 언어팩, 편집기 번들 재빌드, 이력 문서 동반
138 lines
5.5 KiB
PHP
138 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\Public;
|
|
|
|
use App\Enums\ExtensionStatus;
|
|
use App\Enums\LayoutExtensionType;
|
|
use App\Enums\LayoutSourceType;
|
|
use App\Models\LayoutExtension;
|
|
use App\Models\Template;
|
|
use App\Services\LayoutExtensionService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* [case:backend-34] overlay 확장이 주입한 노드의 `__source` 에 `injectionIndex` 가 실린다
|
|
*
|
|
* 편집기 확장 편집 모드는 호스트 병합 트리(`with_source_meta=1`)에서 현재 확장의 노드를
|
|
* 추출해 `injections[].components` 로 되돌려 저장한다. 어느 injection 에서 왔는지가
|
|
* 메타에 없으면 재조립이 모든 노드를 버려 저장본의 injections 가 통째로 비워진다
|
|
* (실측: 이커머스 → `_user_base` 헤더 통화 선택기 18,405B → 148B). 백엔드가 주입 시점에
|
|
* injection 순번을 메타로 싣는 것이 SSoT 다.
|
|
*/
|
|
class LayoutOverlayInjectionIndexMetaTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* @var array<string>
|
|
*/
|
|
protected array $requiredExtensions = [
|
|
'plugins/sirsoft-gdpr',
|
|
];
|
|
|
|
#[Test]
|
|
public function overlay_injected_nodes_carry_injection_index_in_source_meta(): void
|
|
{
|
|
$template = Template::factory()->create([
|
|
'identifier' => 'sirsoft-basic',
|
|
'type' => 'user',
|
|
'status' => ExtensionStatus::Active->value,
|
|
]);
|
|
|
|
$extension = LayoutExtension::create([
|
|
'template_id' => $template->id,
|
|
'extension_type' => LayoutExtensionType::Overlay,
|
|
'target_name' => '_user_base',
|
|
// 출처를 템플릿 자신으로 두어 모듈 활성 여부 게이트(isExtensionSourceActive)에 걸리지 않게 한다.
|
|
'source_type' => LayoutSourceType::Template,
|
|
'source_identifier' => 'sirsoft-basic',
|
|
'content' => [
|
|
'target_layout' => '_user_base',
|
|
'injections' => [
|
|
// 0 — inject_props (components 없음). 순번은 이 injection 도 센다.
|
|
['target_id' => 'anchor_a', 'position' => 'inject_props', 'props' => ['className' => 'x']],
|
|
// 1 — 컴포넌트 주입
|
|
[
|
|
'target_id' => 'anchor_b',
|
|
'position' => 'append_child',
|
|
'components' => [
|
|
[
|
|
'type' => 'basic',
|
|
'name' => 'Span',
|
|
'id' => 'ext_b',
|
|
'children' => [
|
|
['type' => 'basic', 'name' => 'Span', 'id' => 'ext_b_child'],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'priority' => 320,
|
|
],
|
|
'priority' => 320,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$layout = [
|
|
'layout_name' => '_user_base',
|
|
'meta' => ['title' => 'Base'],
|
|
'components' => [
|
|
['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_a'],
|
|
['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_b'],
|
|
],
|
|
];
|
|
|
|
// 편집 모드(with_source_meta) 병합 — PublicLayoutController::serve 가 같은 인자로 호출한다.
|
|
$merged = $this->app->make(LayoutExtensionService::class)
|
|
->applyExtensions($layout, $template->id, true);
|
|
|
|
$injected = $this->findNodeById($merged['components'], 'ext_b');
|
|
$this->assertNotNull($injected, '주입 노드 ext_b 가 병합 결과에 있어야 한다');
|
|
$this->assertSame('extension', $injected['__source']['kind'] ?? null);
|
|
$this->assertSame($extension->id, $injected['__source']['extensionId'] ?? null);
|
|
$this->assertSame(
|
|
1,
|
|
$injected['__source']['injectionIndex'] ?? null,
|
|
'주입 노드 진입점의 __source 에 원래 injection 순번(1)이 실려야 편집기가 저장 시 되돌릴 수 있다'
|
|
);
|
|
|
|
// 자식도 같은 순번을 상속(진입점 판정은 자식으로 내려가지 않지만 메타는 일관).
|
|
$child = $this->findNodeById($merged['components'], 'ext_b_child');
|
|
$this->assertSame(1, $child['__source']['injectionIndex'] ?? null);
|
|
|
|
// 운영 렌더(with_source_meta=false)에는 메타가 전혀 없다 — 비파괴.
|
|
$plain = $this->app->make(LayoutExtensionService::class)
|
|
->applyExtensions($layout, $template->id, false);
|
|
$plainInjected = $this->findNodeById($plain['components'], 'ext_b');
|
|
$this->assertNotNull($plainInjected);
|
|
$this->assertArrayNotHasKey('__source', $plainInjected);
|
|
}
|
|
|
|
/**
|
|
* 트리에서 id 로 노드를 찾는다.
|
|
*
|
|
* @param array<int, mixed>|null $nodes 컴포넌트 배열
|
|
* @param string $id 찾을 id
|
|
* @return array<string, mixed>|null 노드
|
|
*/
|
|
private function findNodeById(?array $nodes, string $id): ?array
|
|
{
|
|
foreach ($nodes ?? [] as $node) {
|
|
if (! is_array($node)) {
|
|
continue;
|
|
}
|
|
if (($node['id'] ?? null) === $id) {
|
|
return $node;
|
|
}
|
|
$found = $this->findNodeById($node['children'] ?? null, $id);
|
|
if ($found !== null) {
|
|
return $found;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|