xref: /dokuwiki/_test/tests/DraftTest.php (revision 884caed926ca0aa0af6ce3f34ae3aa7317a3361a)
1<?php
2
3namespace dokuwiki\test;
4
5use dokuwiki\Draft;
6
7/**
8 * Tests for the draft handling of edit sessions
9 *
10 * @group draft
11 */
12class DraftTest extends \DokuWikiTest
13{
14    /**
15     * Stage the edit-session POST fields the way the editor would send them.
16     *
17     * @param string $wikitext the edited text
18     * @param string $prefix the section prefix (with its trailing boundary newline)
19     * @param string $suffix the section suffix
20     * @param int $date the revision date of the edited page
21     */
22    protected function setEditPost(string $wikitext, string $prefix = '', string $suffix = '', int $date = 0): void
23    {
24        global $INPUT;
25        $INPUT->post->set('wikitext', $wikitext);
26        $INPUT->post->set('prefix', $prefix);
27        $INPUT->post->set('suffix', $suffix);
28        $INPUT->post->set('date', $date);
29    }
30
31    /**
32     * A whole-page edit is saved and read back unchanged.
33     */
34    public function testSaveAndRetrieveWholePage(): void
35    {
36        global $conf;
37        $conf['usedraft'] = 1;
38
39        $this->setEditPost('Hello world');
40        $draft = new Draft('draft:wholepage', 'tester');
41
42        $this->assertTrue($draft->saveDraft());
43        $this->assertTrue($draft->isDraftAvailable());
44        $this->assertFileExists($draft->getDraftFilename());
45        $this->assertSame('Hello world', $draft->getDraftText());
46    }
47
48    /**
49     * A section edit is reassembled from prefix, text and suffix when read back. The editor
50     * posts the prefix with a trailing boundary newline that is stripped on save and
51     * re-inserted on retrieval.
52     */
53    public function testSaveReassemblesSectionEdit(): void
54    {
55        global $conf;
56        $conf['usedraft'] = 1;
57
58        $this->setEditPost('BODY', "PRE\n", 'SUF');
59        $draft = new Draft('draft:section', 'tester');
60        $this->assertTrue($draft->saveDraft());
61
62        $this->assertSame("PRE\nBODY\nSUF", $draft->getDraftText());
63    }
64
65    /**
66     * No draft is written when drafts are disabled in the configuration.
67     */
68    public function testNotSavedWhenDisabled(): void
69    {
70        global $conf;
71        $conf['usedraft'] = 0;
72
73        $this->setEditPost('Hello world');
74        $draft = new Draft('draft:disabled', 'tester');
75
76        $this->assertFalse($draft->saveDraft());
77        $this->assertFalse($draft->isDraftAvailable());
78    }
79
80    /**
81     * No draft is written when nothing was edited and no handler wants the event.
82     */
83    public function testNotSavedWithoutText(): void
84    {
85        global $conf;
86        $conf['usedraft'] = 1;
87
88        $draft = new Draft('draft:notext', 'tester');
89
90        $this->assertFalse($draft->saveDraft());
91        $this->assertFalse($draft->isDraftAvailable());
92    }
93
94    /**
95     * Deleting a draft removes it from disk.
96     */
97    public function testDeleteRemovesTheDraft(): void
98    {
99        global $conf;
100        $conf['usedraft'] = 1;
101
102        $this->setEditPost('Hello world');
103        $draft = new Draft('draft:delete', 'tester');
104        $draft->saveDraft();
105        $this->assertTrue($draft->isDraftAvailable());
106
107        $draft->deleteDraft();
108        $this->assertFalse($draft->isDraftAvailable());
109        $this->assertFileDoesNotExist($draft->getDraftFilename());
110    }
111
112    /**
113     * Reading a draft that does not exist is an error.
114     */
115    public function testGetDraftTextThrowsWhenMissing(): void
116    {
117        $draft = new Draft('draft:missing', 'tester');
118        $this->assertFalse($draft->isDraftAvailable());
119
120        $this->expectException(\RuntimeException::class);
121        $draft->getDraftText();
122    }
123
124    /**
125     * A draft older than the current page revision is stale and gets purged on construction,
126     * so a user is never offered a draft that predates the saved content.
127     */
128    public function testStaleDraftPurgedOnConstruction(): void
129    {
130        global $conf;
131        $conf['usedraft'] = 1;
132
133        $id = 'draft:stale';
134        $this->setEditPost('draft body');
135        $draft = new Draft($id, 'tester');
136        $draft->saveDraft();
137        $file = $draft->getDraftFilename();
138        $this->assertFileExists($file);
139
140        // a newer page revision now exists
141        io_saveFile(wikiFN($id), 'saved by someone else');
142        touch(wikiFN($id), filemtime($file) + 10);
143
144        // re-opening the draft must purge it as stale
145        $fresh = new Draft($id, 'tester');
146        $this->assertFalse($fresh->isDraftAvailable());
147        $this->assertFileDoesNotExist($file);
148    }
149
150    /**
151     * A draft newer than the current page revision is kept on construction.
152     */
153    public function testFreshDraftKeptOnConstruction(): void
154    {
155        global $conf;
156        $conf['usedraft'] = 1;
157
158        $id = 'draft:fresh';
159        // an older page revision exists
160        io_saveFile(wikiFN($id), 'older content');
161
162        $this->setEditPost('newer draft');
163        $draft = new Draft($id, 'tester');
164        $draft->saveDraft();
165        $file = $draft->getDraftFilename();
166        touch($file, filemtime(wikiFN($id)) + 10);
167
168        $fresh = new Draft($id, 'tester');
169        $this->assertTrue($fresh->isDraftAvailable());
170    }
171}
172