diff --git a/app/Node/NodeController.php b/app/Node/NodeController.php index 20d534f..6104327 100644 --- a/app/Node/NodeController.php +++ b/app/Node/NodeController.php @@ -2,9 +2,7 @@ namespace App\Node; -use App\Node\UseCases\BulkCreateNodesRequest; use App\Node\NodeRepository; -use App\Node\UseCases\BulkCreateNodes; use App\Node\UseCases\CreateNode; use App\Node\UseCases\CreateNodeRequest; use App\Text\TextRepository; @@ -77,55 +75,4 @@ class NodeController ])); return $response->withStatus(201)->withHeader('Content-Type', 'application/json'); } - - public function bulkCreateNodes( - Request $request, - Response $response, - BulkCreateNodes $bulkCreateNodesUseCase, - ): Response { - $data = json_decode((string) $request->getBody(), true) ?? []; - - $titlePrefix = trim($data['titlePrefix'] ?? ''); - if ($titlePrefix === '') { - $response->getBody()->write(json_encode(['error' => 'Title prefix is required'])); - return $response->withStatus(400)->withHeader('Content-Type', 'application/json'); - } - - $count = isset($data['count']) ? (int) $data['count'] : 0; - if ($count < 1) { - $response->getBody()->write(json_encode(['error' => 'Count must be at least 1'])); - return $response->withStatus(400)->withHeader('Content-Type', 'application/json'); - } - - if (!isset($data['parentNodeId']) || $data['parentNodeId'] === null) { - $response->getBody()->write(json_encode(['error' => 'parentNodeId is required'])); - return $response->withStatus(400)->withHeader('Content-Type', 'application/json'); - } - - $textId = (int) ($data['textId'] ?? 0); - $parentNodeId = (int) $data['parentNodeId']; - - try { - $nodes = $bulkCreateNodesUseCase->execute(new BulkCreateNodesRequest( - textId: $textId, - parentNodeId: $parentNodeId, - titlePrefix: $titlePrefix, - count: $count, - )); - } catch (DomainException $e) { - $response->getBody()->write(json_encode(['error' => $e->getMessage()])); - return $response->withStatus(404)->withHeader('Content-Type', 'application/json'); - } - - $result = array_map(function ($node) { - return [ - 'id' => $node->getId(), - 'title' => $node->getTitle(), - 'parentNodeId' => $node->getParentNode()?->getId(), - ]; - }, $nodes); - - $response->getBody()->write(json_encode(array_values($result))); - return $response->withStatus(201)->withHeader('Content-Type', 'application/json'); - } } diff --git a/app/Node/UseCases/BulkCreateNodes.php b/app/Node/UseCases/BulkCreateNodes.php deleted file mode 100644 index 1ca322d..0000000 --- a/app/Node/UseCases/BulkCreateNodes.php +++ /dev/null @@ -1,45 +0,0 @@ -textRepo->find($request->textId); - if ($text === null) { - throw new DomainException("Text with id: {$request->textId} doesnt exist"); - } - - $parentNode = $this->nodeRepo->find($request->parentNodeId); - if ($parentNode === null) { - throw new DomainException("Node with id: {$request->parentNodeId} doesnt exist"); - } - - $created = []; - for ($i = 1; $i <= $request->count; $i++) { - $created[] = $this->nodeRepo->create(new CreateNodeDto( - text: $text, - title: "{$request->titlePrefix} {$i}", - parentNode: $parentNode, - )); - } - - return $created; - } -} diff --git a/app/Node/UseCases/BulkCreateNodesRequest.php b/app/Node/UseCases/BulkCreateNodesRequest.php deleted file mode 100644 index 45b94be..0000000 --- a/app/Node/UseCases/BulkCreateNodesRequest.php +++ /dev/null @@ -1,13 +0,0 @@ -get('/api/texts/{textId}', [TextController::class, 'getText']); $app->post('/api/texts', [TextController::class, 'createText']); $app->get('/api/nodes/{textId}', [NodeController::class, 'getNodesOfText']); -$app->post('/api/nodes/bulk', [NodeController::class, 'bulkCreateNodes']); $app->post('/api/nodes', [NodeController::class, 'createNode']); return $app; diff --git a/cypress/e2e/adminTextBulkAdd.cy.js b/cypress/e2e/adminTextBulkAdd.cy.js deleted file mode 100644 index 03159a2..0000000 --- a/cypress/e2e/adminTextBulkAdd.cy.js +++ /dev/null @@ -1,85 +0,0 @@ -describe('Bulk add children on the admin text detail page', () => { - beforeEach(() => { - cy.exec('npm run db:seed') - cy.intercept('GET', '/api/texts/0').as('getText') - cy.intercept('GET', '/api/nodes/0').as('getNodes') - cy.visit('/admin/texts/0') - cy.wait('@getText') - cy.wait('@getNodes') - }) - - afterEach(() => { - cy.exec('npm run db:wipe') - }) - - it('shows a "Bulk add children" button on each node', () => { - cy.get('#text-detail li').each(($li) => { - cy.wrap($li).find('button.bulk-add-children').should('exist') - }) - }) - - it('clicking "Bulk add children" reveals inline form inputs', () => { - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible') - cy.get('#text-detail > ul > li').first().children('input.bulk-count').should('be.visible') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').should('be.visible') - }) - - it('clicking "Bulk add children" again hides the form', () => { - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible') - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('not.exist') - cy.get('#text-detail > ul > li').first().children('input.bulk-count').should('not.exist') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').should('not.exist') - }) - - it('can bulk add children to the root node', () => { - cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate') - cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh') - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page') - cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').click() - cy.wait('@bulkCreate').its('response.statusCode').should('eq', 201) - cy.wait('@getNodesRefresh') - cy.get('#text-detail li').should('contain', 'Page 1') - cy.get('#text-detail li').should('contain', 'Page 2') - cy.get('#text-detail li').should('contain', 'Page 3') - }) - - it('does not submit if title prefix is empty', () => { - cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate') - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').click() - cy.get('@bulkCreate.all').should('have.length', 0) - }) - - it('does not submit if count is empty', () => { - cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate') - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').click() - cy.get('@bulkCreate.all').should('have.length', 0) - }) - - it('bulk added nodes persist after page reload', () => { - cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate') - cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh') - cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click() - cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page') - cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3') - cy.get('#text-detail > ul > li').first().children('button.save-bulk').click() - cy.wait('@bulkCreate') - cy.wait('@getNodesRefresh') - cy.intercept('GET', '/api/texts/0').as('getTextReload') - cy.intercept('GET', '/api/nodes/0').as('getNodesReload') - cy.reload() - cy.wait('@getTextReload') - cy.wait('@getNodesReload') - cy.get('#text-detail li').should('contain', 'Page 1') - cy.get('#text-detail li').should('contain', 'Page 2') - cy.get('#text-detail li').should('contain', 'Page 3') - }) -}) diff --git a/public/js/text.js b/public/js/text.js index f017bd4..fc62744 100644 --- a/public/js/text.js +++ b/public/js/text.js @@ -58,12 +58,6 @@ function renderTree(nodes, textId) { addBtn.addEventListener('click', () => toggleAddForm(li, node.id, textId)); li.appendChild(addBtn); - const bulkBtn = document.createElement('button'); - bulkBtn.textContent = 'Bulk add children'; - bulkBtn.className = 'bulk-add-children'; - bulkBtn.addEventListener('click', () => toggleBulkAddForm(li, node.id, textId)); - li.appendChild(bulkBtn); - if (node.children.length > 0) { li.appendChild(renderTree(node.children, textId)); } @@ -108,48 +102,3 @@ function toggleAddForm(li, parentNodeId, textId) { li.appendChild(input); li.appendChild(saveBtn); } - -function toggleBulkAddForm(li, parentNodeId, textId) { - const existing = li.querySelector('input.bulk-title'); - if (existing) { - existing.remove(); - li.querySelector('input.bulk-count').remove(); - li.querySelector('button.save-bulk').remove(); - return; - } - - const titleInput = document.createElement('input'); - titleInput.type = 'text'; - titleInput.className = 'bulk-title'; - titleInput.placeholder = 'Title prefix'; - - const countInput = document.createElement('input'); - countInput.type = 'number'; - countInput.className = 'bulk-count'; - countInput.placeholder = 'Count'; - countInput.min = '1'; - - const saveBtn = document.createElement('button'); - saveBtn.textContent = 'Save'; - saveBtn.className = 'save-bulk'; - saveBtn.addEventListener('click', () => { - const titlePrefix = titleInput.value.trim(); - const count = parseInt(countInput.value); - if (!titlePrefix || !count || count < 1) return; - - fetch('/api/nodes/bulk', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ textId: parseInt(textId), parentNodeId, titlePrefix, count }), - }) - .then(res => { - if (!res.ok) throw new Error('Failed to bulk create nodes'); - return res.json(); - }) - .then(() => fetchAndRenderNodes(textId)); - }); - - li.appendChild(titleInput); - li.appendChild(countInput); - li.appendChild(saveBtn); -} diff --git a/tests/Unit/Node/UseCases/BulkCreateNodesTest.php b/tests/Unit/Node/UseCases/BulkCreateNodesTest.php deleted file mode 100644 index 13b16d0..0000000 --- a/tests/Unit/Node/UseCases/BulkCreateNodesTest.php +++ /dev/null @@ -1,134 +0,0 @@ -textRepo = new FakeTextRepository; - $this->textRepo->create(new CreateTextDto(name: 'text')); - - $this->nodeRepo = new FakeNodeRepository; - $text = $this->textRepo->find(0); - $this->parentNode = $this->nodeRepo->create(new CreateNodeDto( - text: $text, - title: 'Root', - parentNode: null, - )); - - $this->useCase = new BulkCreateNodes( - nodeRepo: $this->nodeRepo, - textRepo: $this->textRepo, - ); - } - - public function test_creates_correct_number_of_nodes(): void - { - $nodes = $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Page', - count: 5, - )); - - $this->assertCount(5, $nodes); - } - - public function test_nodes_have_correct_titles(): void - { - $nodes = $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Page', - count: 3, - )); - - $this->assertEquals('Page 1', $nodes[0]->getTitle()); - $this->assertEquals('Page 2', $nodes[1]->getTitle()); - $this->assertEquals('Page 3', $nodes[2]->getTitle()); - } - - public function test_nodes_have_correct_parent(): void - { - $nodes = $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Page', - count: 3, - )); - - foreach ($nodes as $node) { - $this->assertEquals($this->parentNode->getId(), $node->getParentNode()->getId()); - } - } - - public function test_nodes_belong_to_text(): void - { - $nodes = $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Page', - count: 3, - )); - - foreach ($nodes as $node) { - $this->assertEquals(0, $node->getText()->getId()); - } - } - - public function test_returns_array_of_node_instances(): void - { - $nodes = $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Chapter', - count: 2, - )); - - foreach ($nodes as $node) { - $this->assertInstanceOf(Node::class, $node); - } - } - - public function test_throws_if_text_doesnt_exist(): void - { - $this->expectException(DomainException::class); - $this->expectExceptionMessage("Text with id: 99 doesnt exist"); - - $this->useCase->execute(new BulkCreateNodesRequest( - textId: 99, - parentNodeId: $this->parentNode->getId(), - titlePrefix: 'Page', - count: 5, - )); - } - - public function test_throws_if_parent_node_doesnt_exist(): void - { - $this->expectException(DomainException::class); - $this->expectExceptionMessage("Node with id: 99 doesnt exist"); - - $this->useCase->execute(new BulkCreateNodesRequest( - textId: 0, - parentNodeId: 99, - titlePrefix: 'Page', - count: 5, - )); - } -} diff --git a/tests/e2e/Controllers/BulkCreateNodesControllerTest.php b/tests/e2e/Controllers/BulkCreateNodesControllerTest.php deleted file mode 100644 index 0ff3f0c..0000000 --- a/tests/e2e/Controllers/BulkCreateNodesControllerTest.php +++ /dev/null @@ -1,218 +0,0 @@ -textRepo = new FakeTextRepository; - $text = $this->textRepo->create(new CreateTextDto(name: 'test text')); - - $this->nodeRepo = new FakeNodeRepository; - $this->nodeRepo->create(new CreateNodeDto( - text: $text, - title: 'Root Node', - parentNode: null, - )); - $this->useCase = new BulkCreateNodes( - $this->nodeRepo, - $this->textRepo - ); - $this->controller = new NodeController( - $this->nodeRepo, - $this->textRepo - ); - } - - private function makeRequest(array $data): ServerRequestInterface - { - $body = (new StreamFactory())->createStream(json_encode($data)); - return (new ServerRequestFactory()) - ->createServerRequest('POST', 'http://localhost/api/nodes/bulk') - ->withHeader('Content-Type', 'application/json') - ->withBody($body); - } - - public function test_bulk_create_nodes_returns_201_with_created_nodes(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - 'count' => 3, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(201, $response->getStatusCode()); - $body = json_decode($response->getBody(), true); - $this->assertIsArray($body); - } - - public function test_bulk_create_nodes_returns_correct_count(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - 'count' => 10, - ]), - new Response(), - $this->useCase, - ); - - $body = json_decode($response->getBody(), true); - $this->assertCount(10, $body); - } - - public function test_bulk_create_nodes_returns_correct_titles(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Chapter', - 'count' => 3, - ]), - new Response(), - $this->useCase, - ); - - $body = json_decode($response->getBody(), true); - $this->assertEquals('Chapter 1', $body[0]['title']); - $this->assertEquals('Chapter 2', $body[1]['title']); - $this->assertEquals('Chapter 3', $body[2]['title']); - } - - public function test_bulk_create_nodes_response_includes_id_and_parent_node_id(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - 'count' => 2, - ]), - new Response(), - $this->useCase, - ); - - $body = json_decode($response->getBody(), true); - $this->assertArrayHasKey('id', $body[0]); - $this->assertEquals(0, $body[0]['parentNodeId']); - } - - public function test_bulk_create_nodes_returns_400_when_title_prefix_missing(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'count' => 5, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(400, $response->getStatusCode()); - } - - public function test_bulk_create_nodes_returns_400_when_count_is_zero(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - 'count' => 0, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(400, $response->getStatusCode()); - } - - public function test_bulk_create_nodes_returns_400_when_count_is_missing(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(400, $response->getStatusCode()); - } - - public function test_bulk_create_nodes_returns_400_when_parent_node_id_missing(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'titlePrefix' => 'Page', - 'count' => 5, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(400, $response->getStatusCode()); - } - - public function test_bulk_create_nodes_returns_404_when_text_not_found(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 99, - 'parentNodeId' => 0, - 'titlePrefix' => 'Page', - 'count' => 5, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(404, $response->getStatusCode()); - } - - public function test_bulk_create_nodes_returns_404_when_parent_node_not_found(): void - { - $response = $this->controller->bulkCreateNodes( - $this->makeRequest([ - 'textId' => 0, - 'parentNodeId' => 99, - 'titlePrefix' => 'Page', - 'count' => 5, - ]), - new Response(), - $this->useCase, - ); - - $this->assertEquals(404, $response->getStatusCode()); - } -}