11import type { Node } from 'prosemirror-model' ;
2- import { TextSelection } from 'prosemirror-state' ;
2+ import { EditorState , TextSelection } from 'prosemirror-state' ;
33import { builders } from 'prosemirror-test-builder' ;
44
55import { ExtensionsManager } from '../../../core' ;
@@ -13,6 +13,7 @@ import {
1313 type Direction ,
1414 findFakeParaPosForTextSelection ,
1515 findNextFakeParaPosForGapCursorSelection ,
16+ selectAll ,
1617} from './commands' ;
1718
1819const { schema} = new ExtensionsManager ( {
@@ -26,11 +27,25 @@ const {schema} = new ExtensionsManager({
2627 spec : { content : `block*` , group : 'block' , gapcursor : false } ,
2728 fromMd : { tokenSpec : { name : 'testnode' , type : 'block' , ignore : true } } ,
2829 toMd : ( ) => { } ,
30+ } ) )
31+ . addNode ( 'selectContentNode' , ( ) => ( {
32+ spec : { content : `block+` , group : 'block' , selectContent : true } ,
33+ fromMd : { tokenSpec : { name : 'selectContentNode' , type : 'block' , ignore : true } } ,
34+ toMd : ( ) => { } ,
2935 } ) ) ,
3036} ) . buildDeps ( ) ;
3137
32- const { doc, p, bq, codeBlock, table, tbody, tr, td, testnode} = builders <
33- 'doc' | 'p' | 'bq' | 'codeBlock' | 'table' | 'tbody' | 'tr' | 'td' | 'testnode'
38+ const { doc, p, bq, codeBlock, table, tbody, tr, td, testnode, selectContentNode} = builders <
39+ | 'doc'
40+ | 'p'
41+ | 'bq'
42+ | 'codeBlock'
43+ | 'table'
44+ | 'tbody'
45+ | 'tr'
46+ | 'td'
47+ | 'testnode'
48+ | 'selectContentNode'
3449> ( schema , {
3550 doc : { nodeType : BaseNode . Doc } ,
3651 p : { nodeType : BaseNode . Paragraph } ,
@@ -253,3 +268,139 @@ describe('Selection arrow commands: findFakeParaPosForTextSelection', () => {
253268 } ,
254269 ) ;
255270} ) ;
271+
272+ describe ( 'selectAll' , ( ) => {
273+ function createState ( document : Node , from : number , to ?: number ) {
274+ return EditorState . create ( {
275+ doc : document ,
276+ selection : TextSelection . create ( document , from , to ?? from ) ,
277+ } ) ;
278+ }
279+
280+ function runSelectAll ( state : EditorState ) : EditorState | null {
281+ let newState : EditorState | null = null ;
282+ selectAll ( state , ( tr ) => {
283+ newState = state . apply ( tr ) ;
284+ } ) ;
285+ return newState ;
286+ }
287+
288+ describe ( 'code block (spec.code)' , ( ) => {
289+ it ( 'should select all content inside code block when cursor is inside' , ( ) => {
290+ // doc: <cb>hello</cb> positions: 0[cb]1 h e l l o 6[/cb]7
291+ const d = doc ( codeBlock ( 'hello' ) ) ;
292+ const state = createState ( d , 3 ) ; // cursor in the middle of "hello"
293+ const result = runSelectAll ( state ) ;
294+
295+ expect ( result ) . toBeTruthy ( ) ;
296+ expect ( result ! . selection . from ) . toBe ( 1 ) ;
297+ expect ( result ! . selection . to ) . toBe ( 6 ) ;
298+ } ) ;
299+
300+ it ( 'should return false when entire code block content is already selected' , ( ) => {
301+ const d = doc ( codeBlock ( 'hello' ) ) ;
302+ const state = createState ( d , 1 , 6 ) ; // entire content selected
303+ const result = runSelectAll ( state ) ;
304+
305+ expect ( result ) . toBeNull ( ) ;
306+ } ) ;
307+
308+ it ( 'should select code block content when partial selection exists' , ( ) => {
309+ const d = doc ( codeBlock ( 'hello' ) ) ;
310+ const state = createState ( d , 2 , 4 ) ; // partial selection "ell"
311+ const result = runSelectAll ( state ) ;
312+
313+ expect ( result ) . toBeTruthy ( ) ;
314+ expect ( result ! . selection . from ) . toBe ( 1 ) ;
315+ expect ( result ! . selection . to ) . toBe ( 6 ) ;
316+ } ) ;
317+ } ) ;
318+
319+ describe ( 'empty content' , ( ) => {
320+ it ( 'should skip empty code block' , ( ) => {
321+ const d = doc ( codeBlock ( ) ) ;
322+ const state = createState ( d , 1 ) ; // cursor in empty code block
323+ const result = runSelectAll ( state ) ;
324+
325+ expect ( result ) . toBeNull ( ) ;
326+ } ) ;
327+
328+ it ( 'should skip selectContent node with empty paragraph' , ( ) => {
329+ const d = doc ( selectContentNode ( p ( ) ) ) ;
330+ const state = createState ( d , 2 ) ; // cursor in empty paragraph
331+ const result = runSelectAll ( state ) ;
332+
333+ expect ( result ) . toBeNull ( ) ;
334+ } ) ;
335+
336+ it ( 'should select content of selectContent node with non-empty paragraph' , ( ) => {
337+ const d = doc ( selectContentNode ( p ( 'text' ) ) ) ;
338+ const state = createState ( d , 3 ) ; // cursor in "text"
339+ const result = runSelectAll ( state ) ;
340+
341+ expect ( result ) . toBeTruthy ( ) ;
342+ expect ( result ! . selection . from ) . toBe ( 1 ) ;
343+ expect ( result ! . selection . to ) . toBe ( 7 ) ;
344+ } ) ;
345+ } ) ;
346+
347+ describe ( 'selectContent with multiple paragraphs' , ( ) => {
348+ // selectContentNode(p('hello'), p('world'))
349+ // positions: 0[scn]1[p]2 hello 7[/p]8[p]9 world 14[/p]15[/scn]16
350+
351+ it ( 'should select all content when cursor is inside' , ( ) => {
352+ const d = doc ( selectContentNode ( p ( 'hello' ) , p ( 'world' ) ) ) ;
353+ const state = createState ( d , 4 ) ; // cursor in "hello"
354+ const result = runSelectAll ( state ) ;
355+
356+ expect ( result ) . toBeTruthy ( ) ;
357+ expect ( result ! . selection . from ) . toBe ( 1 ) ;
358+ expect ( result ! . selection . to ) . toBe ( 15 ) ;
359+ } ) ;
360+
361+ it ( 'should fall through when all text is mouse-selected (resolved positions)' , ( ) => {
362+ const d = doc ( selectContentNode ( p ( 'hello' ) , p ( 'world' ) ) ) ;
363+ // mouse selection covers from start of first paragraph text to end of last
364+ const state = createState ( d , 2 , 14 ) ;
365+ const result = runSelectAll ( state ) ;
366+
367+ expect ( result ) . toBeNull ( ) ;
368+ } ) ;
369+
370+ it ( 'should fall through when content is fully selected via structural boundaries' , ( ) => {
371+ const d = doc ( selectContentNode ( p ( 'hello' ) , p ( 'world' ) ) ) ;
372+ const state = createState ( d , 1 , 15 ) ;
373+ const result = runSelectAll ( state ) ;
374+
375+ expect ( result ) . toBeNull ( ) ;
376+ } ) ;
377+
378+ it ( 'should select all content when only partial text is selected' , ( ) => {
379+ const d = doc ( selectContentNode ( p ( 'hello' ) , p ( 'world' ) ) ) ;
380+ const state = createState ( d , 3 , 12 ) ; // partial selection
381+ const result = runSelectAll ( state ) ;
382+
383+ expect ( result ) . toBeTruthy ( ) ;
384+ expect ( result ! . selection . from ) . toBe ( 1 ) ;
385+ expect ( result ! . selection . to ) . toBe ( 15 ) ;
386+ } ) ;
387+ } ) ;
388+
389+ describe ( 'no matching nodes' , ( ) => {
390+ it ( 'should return false when cursor is in a regular paragraph' , ( ) => {
391+ const d = doc ( p ( 'hello' ) ) ;
392+ const state = createState ( d , 3 ) ;
393+ const result = runSelectAll ( state ) ;
394+
395+ expect ( result ) . toBeNull ( ) ;
396+ } ) ;
397+
398+ it ( 'should return false when cursor is in a blockquote' , ( ) => {
399+ const d = doc ( bq ( p ( 'hello' ) ) ) ;
400+ const state = createState ( d , 4 ) ;
401+ const result = runSelectAll ( state ) ;
402+
403+ expect ( result ) . toBeNull ( ) ;
404+ } ) ;
405+ } ) ;
406+ } ) ;
0 commit comments