Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
021bb329
Commit
021bb329
authored
Dec 15, 2021
by
Denys Mishunov
Committed by
David O'Regan
Dec 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Source Editor refactoring integration
parent
783ce2d8
Changes
27
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
1060 additions
and
1148 deletions
+1060
-1148
app/assets/javascripts/blob_edit/edit_blob.js
app/assets/javascripts/blob_edit/edit_blob.js
+25
-18
app/assets/javascripts/editor/constants.js
app/assets/javascripts/editor/constants.js
+4
-0
app/assets/javascripts/editor/extensions/example_source_editor_extension.js
...ipts/editor/extensions/example_source_editor_extension.js
+10
-0
app/assets/javascripts/editor/extensions/source_editor_ci_schema_ext.js
...ascripts/editor/extensions/source_editor_ci_schema_ext.js
+21
-26
app/assets/javascripts/editor/extensions/source_editor_extension_base.js
...scripts/editor/extensions/source_editor_extension_base.js
+61
-50
app/assets/javascripts/editor/extensions/source_editor_file_template_ext.js
...ipts/editor/extensions/source_editor_file_template_ext.js
+12
-4
app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
...vascripts/editor/extensions/source_editor_markdown_ext.js
+92
-87
app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
...itor/extensions/source_editor_markdown_livepreview_ext.js
+67
-54
app/assets/javascripts/editor/extensions/source_editor_webide_ext.js
...javascripts/editor/extensions/source_editor_webide_ext.js
+147
-125
app/assets/javascripts/editor/extensions/source_editor_yaml_ext.js
...s/javascripts/editor/extensions/source_editor_yaml_ext.js
+147
-132
app/assets/javascripts/editor/source_editor.js
app/assets/javascripts/editor/source_editor.js
+54
-77
app/assets/javascripts/editor/source_editor_extension.js
app/assets/javascripts/editor/source_editor_extension.js
+1
-1
app/assets/javascripts/editor/source_editor_instance.js
app/assets/javascripts/editor/source_editor_instance.js
+39
-33
app/assets/javascripts/ide/components/repo_editor.vue
app/assets/javascripts/ide/components/repo_editor.vue
+21
-18
app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue
...scripts/pipeline_editor/components/editor/text_editor.vue
+1
-1
spec/frontend/blob_edit/edit_blob_spec.js
spec/frontend/blob_edit/edit_blob_spec.js
+24
-11
spec/frontend/editor/helpers.js
spec/frontend/editor/helpers.js
+48
-24
spec/frontend/editor/source_editor_ci_schema_ext_spec.js
spec/frontend/editor/source_editor_ci_schema_ext_spec.js
+1
-1
spec/frontend/editor/source_editor_extension_base_spec.js
spec/frontend/editor/source_editor_extension_base_spec.js
+53
-108
spec/frontend/editor/source_editor_extension_spec.js
spec/frontend/editor/source_editor_extension_spec.js
+1
-1
spec/frontend/editor/source_editor_instance_spec.js
spec/frontend/editor/source_editor_instance_spec.js
+19
-6
spec/frontend/editor/source_editor_markdown_ext_spec.js
spec/frontend/editor/source_editor_markdown_ext_spec.js
+1
-4
spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
...end/editor/source_editor_markdown_livepreview_ext_spec.js
+63
-40
spec/frontend/editor/source_editor_spec.js
spec/frontend/editor/source_editor_spec.js
+64
-245
spec/frontend/editor/source_editor_yaml_ext_spec.js
spec/frontend/editor/source_editor_yaml_ext_spec.js
+48
-28
spec/frontend/ide/components/repo_editor_spec.js
spec/frontend/ide/components/repo_editor_spec.js
+36
-49
spec/frontend/pipeline_editor/components/editor/text_editor_spec.js
...end/pipeline_editor/components/editor/text_editor_spec.js
+0
-5
No files found.
app/assets/javascripts/blob_edit/edit_blob.js
View file @
021bb329
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
{
FileTemplateExtension
}
from
'
~/editor/extensions/source_editor_file_template_ext
'
;
import
{
FileTemplateExtension
}
from
'
~/editor/extensions/source_editor_file_template_ext
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
{
getBlobLanguage
}
from
'
~/editor/utils
'
;
import
{
getBlobLanguage
}
from
'
~/editor/utils
'
;
...
@@ -26,23 +27,29 @@ export default class EditBlob {
...
@@ -26,23 +27,29 @@ export default class EditBlob {
this
.
editor
.
focus
();
this
.
editor
.
focus
();
}
}
fetchMarkdownExtension
()
{
async
fetchMarkdownExtension
()
{
import
(
'
~/editor/extensions/source_editor_markdown_ext
'
)
try
{
.
then
(({
EditorMarkdownExtension
:
MarkdownExtension
}
=
{})
=>
{
const
[
this
.
editor
.
use
(
{
EditorMarkdownExtension
:
MarkdownExtension
},
new
MarkdownExtension
({
{
EditorMarkdownPreviewExtension
:
MarkdownLivePreview
},
instance
:
this
.
editor
,
]
=
await
Promise
.
all
([
previewMarkdownPath
:
this
.
options
.
previewMarkdownPath
,
import
(
'
~/editor/extensions/source_editor_markdown_ext
'
),
}),
import
(
'
~/editor/extensions/source_editor_markdown_livepreview_ext
'
),
);
]);
this
.
hasMarkdownExtension
=
true
;
this
.
editor
.
use
([
addEditorMarkdownListeners
(
this
.
editor
);
{
definition
:
MarkdownExtension
},
})
{
.
catch
((
e
)
=>
definition
:
MarkdownLivePreview
,
createFlash
({
setupOptions
:
{
previewMarkdownPath
:
this
.
options
.
previewMarkdownPath
},
message
:
`
${
BLOB_EDITOR_ERROR
}
:
${
e
}
`
,
},
}),
]);
);
}
catch
(
e
)
{
createFlash
({
message
:
`
${
BLOB_EDITOR_ERROR
}
:
${
e
}
`
,
});
}
this
.
hasMarkdownExtension
=
true
;
addEditorMarkdownListeners
(
this
.
editor
);
}
}
configureMonacoEditor
()
{
configureMonacoEditor
()
{
...
@@ -60,7 +67,7 @@ export default class EditBlob {
...
@@ -60,7 +67,7 @@ export default class EditBlob {
blobPath
:
fileNameEl
.
value
,
blobPath
:
fileNameEl
.
value
,
blobContent
:
editorEl
.
innerText
,
blobContent
:
editorEl
.
innerText
,
});
});
this
.
editor
.
use
(
new
FileTemplateExtension
({
instance
:
this
.
editor
})
);
this
.
editor
.
use
(
[{
definition
:
SourceEditorExtension
},
{
definition
:
FileTemplateExtension
}]
);
fileNameEl
.
addEventListener
(
'
change
'
,
()
=>
{
fileNameEl
.
addEventListener
(
'
change
'
,
()
=>
{
this
.
editor
.
updateModelLanguage
(
fileNameEl
.
value
);
this
.
editor
.
updateModelLanguage
(
fileNameEl
.
value
);
...
...
app/assets/javascripts/editor/constants.js
View file @
021bb329
...
@@ -42,6 +42,10 @@ export const EDITOR_EXTENSION_STORE_IS_MISSING_ERROR = s__(
...
@@ -42,6 +42,10 @@ export const EDITOR_EXTENSION_STORE_IS_MISSING_ERROR = s__(
// EXTENSIONS' CONSTANTS
// EXTENSIONS' CONSTANTS
//
//
// Source Editor Base Extension
export
const
EXTENSION_BASE_LINE_LINK_ANCHOR_CLASS
=
'
link-anchor
'
;
export
const
EXTENSION_BASE_LINE_NUMBERS_CLASS
=
'
line-numbers
'
;
// For CI config schemas the filename must match
// For CI config schemas the filename must match
// '*.gitlab-ci.yml' regardless of project configuration.
// '*.gitlab-ci.yml' regardless of project configuration.
// https://gitlab.com/gitlab-org/gitlab/-/issues/293641
// https://gitlab.com/gitlab-org/gitlab/-/issues/293641
...
...
app/assets/javascripts/editor/extensions/example_source_editor_extension.js
View file @
021bb329
...
@@ -6,6 +6,16 @@
...
@@ -6,6 +6,16 @@
//
//
export
class
MyFancyExtension
{
export
class
MyFancyExtension
{
/**
* A required getter returning the extension's name
* We have to provide it for every extension instead of relying on the built-in
* `name` prop because the prop does not survive the webpack's minification
* and the name mangling.
* @returns {string}
*/
static
get
extensionName
()
{
return
'
MyFancyExtension
'
;
}
/**
/**
* THE LIFE-CYCLE CALLBACKS
* THE LIFE-CYCLE CALLBACKS
*/
*/
...
...
app/assets/javascripts/editor/extensions/source_editor_ci_schema_ext.js
View file @
021bb329
import
ciSchemaPath
from
'
~/editor/schema/ci.json
'
;
import
ciSchemaPath
from
'
~/editor/schema/ci.json
'
;
import
{
registerSchema
}
from
'
~/ide/utils
'
;
import
{
registerSchema
}
from
'
~/ide/utils
'
;
import
{
SourceEditorExtension
}
from
'
./source_editor_extension_base
'
;
export
class
CiSchemaExtension
extends
SourceEditorExtension
{
export
class
CiSchemaExtension
{
/**
static
get
extensionName
()
{
* Registers a syntax schema to the editor based on project
return
'
CiSchema
'
;
* identifier and commit.
}
*
// eslint-disable-next-line class-methods-use-this
* The schema is added to the file that is currently edited
provides
()
{
* in the editor.
return
{
*
registerCiSchema
:
(
instance
)
=>
{
* @param {Object} opts
// In order for workers loaded from `data://` as the
* @param {String} opts.projectNamespace
// ones loaded by monaco editor, we use absolute URLs
* @param {String} opts.projectPath
// to fetch schema files, hence the `gon.gitlab_url`
* @param {String?} opts.ref - Current ref. Defaults to main
// reference. This prevents error:
*/
// "Failed to execute 'fetch' on 'WorkerGlobalScope'"
registerCiSchema
()
{
const
absoluteSchemaUrl
=
gon
.
gitlab_url
+
ciSchemaPath
;
// In order for workers loaded from `data://` as the
const
modelFileName
=
instance
.
getModel
().
uri
.
path
.
split
(
'
/
'
).
pop
();
// ones loaded by monaco editor, we use absolute URLs
// to fetch schema files, hence the `gon.gitlab_url`
// reference. This prevents error:
// "Failed to execute 'fetch' on 'WorkerGlobalScope'"
const
absoluteSchemaUrl
=
gon
.
gitlab_url
+
ciSchemaPath
;
const
modelFileName
=
this
.
getModel
().
uri
.
path
.
split
(
'
/
'
).
pop
();
registerSchema
({
registerSchema
({
uri
:
absoluteSchemaUrl
,
uri
:
absoluteSchemaUrl
,
fileMatch
:
[
modelFileName
],
fileMatch
:
[
modelFileName
],
});
});
},
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_extension_base.js
View file @
021bb329
import
{
Range
}
from
'
monaco-editor
'
;
import
{
Range
}
from
'
monaco-editor
'
;
import
{
waitForCSSLoaded
}
from
'
~/helpers/startup_css_helper
'
;
import
{
import
{
ERROR_INSTANCE_REQUIRED_FOR_EXTENSION
,
EDITOR_TYPE_CODE
}
from
'
../constants
'
;
EDITOR_TYPE_CODE
,
EXTENSION_BASE_LINE_LINK_ANCHOR_CLASS
,
EXTENSION_BASE_LINE_NUMBERS_CLASS
,
}
from
'
../constants
'
;
const
hashRegexp
=
new
RegExp
(
'
#?L
'
,
'
g
'
);
const
hashRegexp
=
new
RegExp
(
'
#?L
'
,
'
g
'
);
const
createAnchor
=
(
href
)
=>
{
const
createAnchor
=
(
href
)
=>
{
const
fragment
=
new
DocumentFragment
();
const
fragment
=
new
DocumentFragment
();
const
el
=
document
.
createElement
(
'
a
'
);
const
el
=
document
.
createElement
(
'
a
'
);
el
.
classList
.
add
(
'
link-anchor
'
);
el
.
classList
.
add
(
EXTENSION_BASE_LINE_LINK_ANCHOR_CLASS
);
el
.
href
=
href
;
el
.
href
=
href
;
fragment
.
appendChild
(
el
);
fragment
.
appendChild
(
el
);
el
.
addEventListener
(
'
contextmenu
'
,
(
e
)
=>
{
el
.
addEventListener
(
'
contextmenu
'
,
(
e
)
=>
{
...
@@ -17,38 +20,46 @@ const createAnchor = (href) => {
...
@@ -17,38 +20,46 @@ const createAnchor = (href) => {
};
};
export
class
SourceEditorExtension
{
export
class
SourceEditorExtension
{
constructor
({
instance
,
...
options
}
=
{})
{
static
get
extensionName
()
{
if
(
instance
)
{
return
'
BaseExtension
'
;
Object
.
assign
(
instance
,
options
);
}
SourceEditorExtension
.
highlightLines
(
instance
);
if
(
instance
.
getEditorType
&&
instance
.
getEditorType
()
===
EDITOR_TYPE_CODE
)
{
// eslint-disable-next-line class-methods-use-this
SourceEditorExtension
.
setupLineLinking
(
instance
);
onUse
(
instance
)
{
}
SourceEditorExtension
.
highlightLines
(
instance
);
SourceEditorExtension
.
deferRerender
(
instance
);
if
(
instance
.
getEditorType
&&
instance
.
getEditorType
()
===
EDITOR_TYPE_CODE
)
{
}
else
if
(
Object
.
entries
(
options
).
length
)
{
SourceEditorExtension
.
setupLineLinking
(
instance
);
throw
new
Error
(
ERROR_INSTANCE_REQUIRED_FOR_EXTENSION
);
}
}
}
}
static
deferRerender
(
instance
)
{
static
onMouseMoveHandler
(
e
)
{
waitForCSSLoaded
(()
=>
{
const
target
=
e
.
target
.
element
;
instance
.
layout
();
if
(
target
.
classList
.
contains
(
EXTENSION_BASE_LINE_NUMBERS_CLASS
))
{
});
const
lineNum
=
e
.
target
.
position
.
lineNumber
;
const
hrefAttr
=
`#L
${
lineNum
}
`
;
let
lineLink
=
target
.
querySelector
(
'
a
'
);
if
(
!
lineLink
)
{
lineLink
=
createAnchor
(
hrefAttr
);
target
.
appendChild
(
lineLink
);
}
}
}
}
static
removeHighlights
(
instance
)
{
static
setupLineLinking
(
instance
)
{
Object
.
assign
(
instance
,
{
instance
.
onMouseMove
(
SourceEditorExtension
.
onMouseMoveHandler
);
lineDecorations
:
instance
.
deltaDecorations
(
instance
.
lineDecorations
||
[],
[]),
instance
.
onMouseDown
((
e
)
=>
{
const
isCorrectAnchor
=
e
.
target
.
element
.
classList
.
contains
(
EXTENSION_BASE_LINE_LINK_ANCHOR_CLASS
,
);
if
(
!
isCorrectAnchor
)
{
return
;
}
if
(
instance
.
lineDecorations
)
{
instance
.
deltaDecorations
(
instance
.
lineDecorations
,
[]);
}
});
});
}
}
/**
* Returns a function that can only be invoked once between
* each browser screen repaint.
* @param {Object} instance - The Source Editor instance
* @param {Array} bounds - The [start, end] array with start
* and end coordinates for highlighting
*/
static
highlightLines
(
instance
,
bounds
=
null
)
{
static
highlightLines
(
instance
,
bounds
=
null
)
{
const
[
start
,
end
]
=
const
[
start
,
end
]
=
bounds
&&
Array
.
isArray
(
bounds
)
bounds
&&
Array
.
isArray
(
bounds
)
...
@@ -74,29 +85,29 @@ export class SourceEditorExtension {
...
@@ -74,29 +85,29 @@ export class SourceEditorExtension {
}
}
}
}
static
onMouseMoveHandler
(
e
)
{
// eslint-disable-next-line class-methods-use-this
const
target
=
e
.
target
.
element
;
provides
()
{
if
(
target
.
classList
.
contains
(
'
line-numbers
'
))
{
return
{
const
lineNum
=
e
.
target
.
position
.
lineNumber
;
/**
const
hrefAttr
=
`#L
${
lineNum
}
`
;
* Removes existing line decorations and updates the reference on the instance
let
el
=
target
.
querySelector
(
'
a
'
);
* @param {module:source_editor_instance~EditorInstance} instance - The Source Editor instance
if
(
!
el
)
{
*/
el
=
createAnchor
(
hrefAttr
);
removeHighlights
:
(
instance
)
=>
{
target
.
appendChild
(
el
);
Object
.
assign
(
instance
,
{
}
lineDecorations
:
instance
.
deltaDecorations
(
instance
.
lineDecorations
||
[],
[]),
}
});
}
},
static
setupLineLinking
(
instance
)
{
/**
instance
.
onMouseMove
(
SourceEditorExtension
.
onMouseMoveHandler
);
* Returns a function that can only be invoked once between
instance
.
onMouseDown
((
e
)
=>
{
* each browser screen repaint.
const
isCorrectAnchor
=
e
.
target
.
element
.
classList
.
contains
(
'
link-anchor
'
);
* @param {Array} bounds - The [start, end] array with start
if
(
!
isCorrectAnchor
)
{
* @param {module:source_editor_instance~EditorInstance} instance - The Source Editor instance
return
;
* and end coordinates for highlighting
}
*/
if
(
instance
.
lineDecorations
)
{
highlightLines
(
instance
,
bounds
=
null
)
{
instance
.
deltaDecorations
(
instance
.
lineDecorations
,
[]
);
SourceEditorExtension
.
highlightLines
(
instance
,
bounds
);
}
}
,
}
)
;
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_file_template_ext.js
View file @
021bb329
import
{
Position
}
from
'
monaco-editor
'
;
import
{
Position
}
from
'
monaco-editor
'
;
import
{
SourceEditorExtension
}
from
'
./source_editor_extension_base
'
;
export
class
FileTemplateExtension
extends
SourceEditorExtension
{
export
class
FileTemplateExtension
{
navigateFileStart
()
{
static
get
extensionName
()
{
this
.
setPosition
(
new
Position
(
1
,
1
));
return
'
FileTemplate
'
;
}
// eslint-disable-next-line class-methods-use-this
provides
()
{
return
{
navigateFileStart
:
(
instance
)
=>
{
instance
.
setPosition
(
new
Position
(
1
,
1
));
},
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
View file @
021bb329
import
{
EditorMarkdownPreviewExtension
}
from
'
~/editor/extensions/source_editor_markdown_livepreview_ext
'
;
export
class
EditorMarkdownExtension
{
static
get
extensionName
()
{
export
class
EditorMarkdownExtension
extends
EditorMarkdownPreviewExtension
{
return
'
EditorMarkdown
'
;
getSelectedText
(
selection
=
this
.
getSelection
())
{
const
{
startLineNumber
,
endLineNumber
,
startColumn
,
endColumn
}
=
selection
;
const
valArray
=
this
.
getValue
().
split
(
'
\n
'
);
let
text
=
''
;
if
(
startLineNumber
===
endLineNumber
)
{
text
=
valArray
[
startLineNumber
-
1
].
slice
(
startColumn
-
1
,
endColumn
-
1
);
}
else
{
const
startLineText
=
valArray
[
startLineNumber
-
1
].
slice
(
startColumn
-
1
);
const
endLineText
=
valArray
[
endLineNumber
-
1
].
slice
(
0
,
endColumn
-
1
);
for
(
let
i
=
startLineNumber
,
k
=
endLineNumber
-
1
;
i
<
k
;
i
+=
1
)
{
text
+=
`
${
valArray
[
i
]}
`
;
if
(
i
!==
k
-
1
)
text
+=
`\n`
;
}
text
=
text
?
[
startLineText
,
text
,
endLineText
].
join
(
'
\n
'
)
:
[
startLineText
,
endLineText
].
join
(
'
\n
'
);
}
return
text
;
}
}
replaceSelectedText
(
text
,
select
=
undefined
)
{
// eslint-disable-next-line class-methods-use-this
const
forceMoveMarkers
=
!
select
;
provides
()
{
this
.
executeEdits
(
''
,
[{
range
:
this
.
getSelection
(),
text
,
forceMoveMarkers
}]);
return
{
}
getSelectedText
:
(
instance
,
selection
=
instance
.
getSelection
())
=>
{
const
{
startLineNumber
,
endLineNumber
,
startColumn
,
endColumn
}
=
selection
;
moveCursor
(
dx
=
0
,
dy
=
0
)
{
const
valArray
=
instance
.
getValue
().
split
(
'
\n
'
);
const
pos
=
this
.
getPosition
();
let
text
=
''
;
pos
.
column
+=
dx
;
if
(
startLineNumber
===
endLineNumber
)
{
pos
.
lineNumber
+=
dy
;
text
=
valArray
[
startLineNumber
-
1
].
slice
(
startColumn
-
1
,
endColumn
-
1
);
this
.
setPosition
(
pos
);
}
else
{
}
const
startLineText
=
valArray
[
startLineNumber
-
1
].
slice
(
startColumn
-
1
);
const
endLineText
=
valArray
[
endLineNumber
-
1
].
slice
(
0
,
endColumn
-
1
);
/**
for
(
let
i
=
startLineNumber
,
k
=
endLineNumber
-
1
;
i
<
k
;
i
+=
1
)
{
* Adjust existing selection to select text within the original selection.
text
+=
`
${
valArray
[
i
]}
`
;
* - If `selectedText` is not supplied, we fetch selected text with
if
(
i
!==
k
-
1
)
text
+=
`\n`
;
*
}
* ALGORITHM:
text
=
text
*
?
[
startLineText
,
text
,
endLineText
].
join
(
'
\n
'
)
* MULTI-LINE SELECTION
:
[
startLineText
,
endLineText
].
join
(
'
\n
'
);
* 1. Find line that contains `toSelect` text.
}
* 2. Using the index of this line and the position of `toSelect` text in it,
return
text
;
* construct:
},
* * newStartLineNumber
replaceSelectedText
:
(
instance
,
text
,
select
)
=>
{
* * newStartColumn
const
forceMoveMarkers
=
!
select
;
*
instance
.
executeEdits
(
''
,
[{
range
:
instance
.
getSelection
(),
text
,
forceMoveMarkers
}]);
* SINGLE-LINE SELECTION
},
* 1. Use `startLineNumber` from the current selection as `newStartLineNumber`
moveCursor
:
(
instance
,
dx
=
0
,
dy
=
0
)
=>
{
* 2. Find the position of `toSelect` text in it to get `newStartColumn`
const
pos
=
instance
.
getPosition
();
*
pos
.
column
+=
dx
;
* 3. `newEndLineNumber` — Since this method is supposed to be used with
pos
.
lineNumber
+=
dy
;
* markdown decorators that are pretty short, the `newEndLineNumber` is
instance
.
setPosition
(
pos
);
* suggested to be assumed the same as the startLine.
},
* 4. `newEndColumn` — pretty obvious
/**
* 5. Adjust the start and end positions of the current selection
* Adjust existing selection to select text within the original selection.
* 6. Re-set selection on the instance
* - If `selectedText` is not supplied, we fetch selected text with
*
*
* @param {string} toSelect - New text to select within current selection.
* ALGORITHM:
* @param {string} selectedText - Currently selected text. It's just a
*
* shortcut: If it's not supplied, we fetch selected text from the instance
* MULTI-LINE SELECTION
*/
* 1. Find line that contains `toSelect` text.
selectWithinSelection
(
toSelect
,
selectedText
)
{
* 2. Using the index of this line and the position of `toSelect` text in it,
const
currentSelection
=
this
.
getSelection
();
* construct:
if
(
currentSelection
.
isEmpty
()
||
!
toSelect
)
{
* * newStartLineNumber
return
;
* * newStartColumn
}
*
const
text
=
selectedText
||
this
.
getSelectedText
(
currentSelection
);
* SINGLE-LINE SELECTION
let
lineShift
;
* 1. Use `startLineNumber` from the current selection as `newStartLineNumber`
let
newStartLineNumber
;
* 2. Find the position of `toSelect` text in it to get `newStartColumn`
let
newStartColumn
;
*
* 3. `newEndLineNumber` — Since this method is supposed to be used with
* markdown decorators that are pretty short, the `newEndLineNumber` is
* suggested to be assumed the same as the startLine.
* 4. `newEndColumn` — pretty obvious
* 5. Adjust the start and end positions of the current selection
* 6. Re-set selection on the instance
*
* @param {module:source_editor_instance~EditorInstance} instance - The Source Editor instance. Is passed automatically.
* @param {string} toSelect - New text to select within current selection.
* @param {string} selectedText - Currently selected text. It's just a
* shortcut: If it's not supplied, we fetch selected text from the instance
*/
selectWithinSelection
:
(
instance
,
toSelect
,
selectedText
)
=>
{
const
currentSelection
=
instance
.
getSelection
();
if
(
currentSelection
.
isEmpty
()
||
!
toSelect
)
{
return
;
}
const
text
=
selectedText
||
instance
.
getSelectedText
(
currentSelection
);
let
lineShift
;
let
newStartLineNumber
;
let
newStartColumn
;
const
textLines
=
text
.
split
(
'
\n
'
);
const
textLines
=
text
.
split
(
'
\n
'
);
if
(
textLines
.
length
>
1
)
{
if
(
textLines
.
length
>
1
)
{
// Multi-line selection
// Multi-line selection
lineShift
=
textLines
.
findIndex
((
line
)
=>
line
.
indexOf
(
toSelect
)
!==
-
1
);
lineShift
=
textLines
.
findIndex
((
line
)
=>
line
.
indexOf
(
toSelect
)
!==
-
1
);
newStartLineNumber
=
currentSelection
.
startLineNumber
+
lineShift
;
newStartLineNumber
=
currentSelection
.
startLineNumber
+
lineShift
;
newStartColumn
=
textLines
[
lineShift
].
indexOf
(
toSelect
)
+
1
;
newStartColumn
=
textLines
[
lineShift
].
indexOf
(
toSelect
)
+
1
;
}
else
{
}
else
{
// Single-line selection
// Single-line selection
newStartLineNumber
=
currentSelection
.
startLineNumber
;
newStartLineNumber
=
currentSelection
.
startLineNumber
;
newStartColumn
=
currentSelection
.
startColumn
+
text
.
indexOf
(
toSelect
);
newStartColumn
=
currentSelection
.
startColumn
+
text
.
indexOf
(
toSelect
);
}
}
const
newEndLineNumber
=
newStartLineNumber
;
const
newEndLineNumber
=
newStartLineNumber
;
const
newEndColumn
=
newStartColumn
+
toSelect
.
length
;
const
newEndColumn
=
newStartColumn
+
toSelect
.
length
;
const
newSelection
=
currentSelection
const
newSelection
=
currentSelection
.
setStartPosition
(
newStartLineNumber
,
newStartColumn
)
.
setStartPosition
(
newStartLineNumber
,
newStartColumn
)
.
setEndPosition
(
newEndLineNumber
,
newEndColumn
);
.
setEndPosition
(
newEndLineNumber
,
newEndColumn
);
this
.
setSelection
(
newSelection
);
instance
.
setSelection
(
newSelection
);
},
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
View file @
021bb329
...
@@ -12,9 +12,8 @@ import {
...
@@ -12,9 +12,8 @@ import {
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS
,
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS
,
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
,
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
,
}
from
'
../constants
'
;
}
from
'
../constants
'
;
import
{
SourceEditorExtension
}
from
'
./source_editor_extension_base
'
;
const
get
Preview
=
(
text
,
previewMarkdownPath
)
=>
{
const
fetch
Preview
=
(
text
,
previewMarkdownPath
)
=>
{
return
axios
return
axios
.
post
(
previewMarkdownPath
,
{
.
post
(
previewMarkdownPath
,
{
text
,
text
,
...
@@ -34,19 +33,20 @@ const setupDomElement = ({ injectToEl = null } = {}) => {
...
@@ -34,19 +33,20 @@ const setupDomElement = ({ injectToEl = null } = {}) => {
return
previewEl
;
return
previewEl
;
};
};
export
class
EditorMarkdownPreviewExtension
extends
SourceEditorExtension
{
export
class
EditorMarkdownPreviewExtension
{
constructor
({
instance
,
previewMarkdownPath
,
...
args
}
=
{})
{
static
get
extensionName
()
{
super
({
instance
,
...
args
});
return
'
EditorMarkdownPreview
'
;
Object
.
assign
(
instance
,
{
}
previewMarkdownPath
,
preview
:
{
onSetup
(
instance
,
setupOptions
)
{
el
:
undefined
,
this
.
preview
=
{
action
:
undefined
,
el
:
undefined
,
shown
:
false
,
action
:
undefined
,
modelChangeListener
:
undefined
,
shown
:
false
,
},
modelChangeListener
:
undefined
,
});
path
:
setupOptions
.
previewMarkdownPath
,
this
.
setupPreviewAction
.
call
(
instance
);
};
this
.
setupPreviewAction
(
instance
);
instance
.
getModel
().
onDidChangeLanguage
(({
newLanguage
,
oldLanguage
}
=
{})
=>
{
instance
.
getModel
().
onDidChangeLanguage
(({
newLanguage
,
oldLanguage
}
=
{})
=>
{
if
(
newLanguage
===
'
markdown
'
&&
oldLanguage
!==
newLanguage
)
{
if
(
newLanguage
===
'
markdown
'
&&
oldLanguage
!==
newLanguage
)
{
...
@@ -68,43 +68,31 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
...
@@ -68,43 +68,31 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
});
});
}
}
static
togglePreviewLayout
(
)
{
togglePreviewLayout
(
instance
)
{
const
{
width
,
height
}
=
this
.
getLayoutInfo
();
const
{
width
,
height
}
=
instance
.
getLayoutInfo
();
const
newWidth
=
this
.
preview
.
shown
const
newWidth
=
this
.
preview
.
shown
?
width
/
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH
?
width
/
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH
:
width
*
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH
;
:
width
*
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH
;
this
.
layout
({
width
:
newWidth
,
height
});
instance
.
layout
({
width
:
newWidth
,
height
});
}
}
static
togglePreviewPanel
(
)
{
togglePreviewPanel
(
instance
)
{
const
parentEl
=
this
.
getDomNode
().
parentElement
;
const
parentEl
=
instance
.
getDomNode
().
parentElement
;
const
{
el
:
previewEl
}
=
this
.
preview
;
const
{
el
:
previewEl
}
=
this
.
preview
;
parentEl
.
classList
.
toggle
(
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS
);
parentEl
.
classList
.
toggle
(
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS
);
if
(
previewEl
.
style
.
display
===
'
none
'
)
{
if
(
previewEl
.
style
.
display
===
'
none
'
)
{
// Show the preview panel
// Show the preview panel
this
.
fetchPreview
();
this
.
fetchPreview
(
instance
);
}
else
{
}
else
{
// Hide the preview panel
// Hide the preview panel
previewEl
.
style
.
display
=
'
none
'
;
previewEl
.
style
.
display
=
'
none
'
;
}
}
}
}
cleanup
()
{
fetchPreview
(
instance
)
{
if
(
this
.
preview
.
modelChangeListener
)
{
this
.
preview
.
modelChangeListener
.
dispose
();
}
this
.
preview
.
action
.
dispose
();
if
(
this
.
preview
.
shown
)
{
EditorMarkdownPreviewExtension
.
togglePreviewPanel
.
call
(
this
);
EditorMarkdownPreviewExtension
.
togglePreviewLayout
.
call
(
this
);
}
this
.
preview
.
shown
=
false
;
}
fetchPreview
()
{
const
{
el
:
previewEl
}
=
this
.
preview
;
const
{
el
:
previewEl
}
=
this
.
preview
;
getPreview
(
this
.
getValue
(),
this
.
previewMarkdownP
ath
)
fetchPreview
(
instance
.
getValue
(),
this
.
preview
.
p
ath
)
.
then
((
data
)
=>
{
.
then
((
data
)
=>
{
previewEl
.
innerHTML
=
sanitize
(
data
);
previewEl
.
innerHTML
=
sanitize
(
data
);
syntaxHighlight
(
previewEl
.
querySelectorAll
(
'
.js-syntax-highlight
'
));
syntaxHighlight
(
previewEl
.
querySelectorAll
(
'
.js-syntax-highlight
'
));
...
@@ -113,10 +101,10 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
...
@@ -113,10 +101,10 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
.
catch
(()
=>
createFlash
(
BLOB_PREVIEW_ERROR
));
.
catch
(()
=>
createFlash
(
BLOB_PREVIEW_ERROR
));
}
}
setupPreviewAction
()
{
setupPreviewAction
(
instance
)
{
if
(
this
.
getAction
(
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
))
return
;
if
(
instance
.
getAction
(
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
))
return
;
this
.
preview
.
action
=
this
.
addAction
({
this
.
preview
.
action
=
instance
.
addAction
({
id
:
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
,
id
:
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
,
label
:
__
(
'
Preview Markdown
'
),
label
:
__
(
'
Preview Markdown
'
),
keybindings
:
[
keybindings
:
[
...
@@ -128,27 +116,52 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
...
@@ -128,27 +116,52 @@ export class EditorMarkdownPreviewExtension extends SourceEditorExtension {
// Method that will be executed when the action is triggered.
// Method that will be executed when the action is triggered.
// @param ed The editor instance is passed in as a convenience
// @param ed The editor instance is passed in as a convenience
run
(
inst
ance
)
{
run
(
inst
)
{
inst
ance
.
togglePreview
();
inst
.
togglePreview
();
},
},
});
});
}
}
togglePreview
()
{
provides
()
{
if
(
!
this
.
preview
?.
el
)
{
return
{
this
.
preview
.
el
=
setupDomElement
({
injectToEl
:
this
.
getDomNode
().
parentElement
});
markdownPreview
:
this
.
preview
,
}
EditorMarkdownPreviewExtension
.
togglePreviewLayout
.
call
(
this
);
EditorMarkdownPreviewExtension
.
togglePreviewPanel
.
call
(
this
);
if
(
!
this
.
preview
?.
shown
)
{
cleanup
:
(
instance
)
=>
{
this
.
preview
.
modelChangeListener
=
this
.
onDidChangeModelContent
(
if
(
this
.
preview
.
modelChangeListener
)
{
debounce
(
this
.
fetchPreview
.
bind
(
this
),
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
),
this
.
preview
.
modelChangeListener
.
dispose
();
);
}
}
else
{
this
.
preview
.
action
.
dispose
();
this
.
preview
.
modelChangeListener
.
dispose
();
if
(
this
.
preview
.
shown
)
{
}
this
.
togglePreviewPanel
(
instance
);
this
.
togglePreviewLayout
(
instance
);
}
this
.
preview
.
shown
=
false
;
},
fetchPreview
:
(
instance
)
=>
this
.
fetchPreview
(
instance
),
this
.
preview
.
shown
=
!
this
.
preview
?.
shown
;
setupPreviewAction
:
(
instance
)
=>
this
.
setupPreviewAction
(
instance
),
togglePreview
:
(
instance
)
=>
{
if
(
!
this
.
preview
?.
el
)
{
this
.
preview
.
el
=
setupDomElement
({
injectToEl
:
instance
.
getDomNode
().
parentElement
});
}
this
.
togglePreviewLayout
(
instance
);
this
.
togglePreviewPanel
(
instance
);
if
(
!
this
.
preview
?.
shown
)
{
this
.
preview
.
modelChangeListener
=
instance
.
onDidChangeModelContent
(
debounce
(
this
.
fetchPreview
.
bind
(
this
,
instance
),
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
,
),
);
}
else
{
this
.
preview
.
modelChangeListener
.
dispose
();
}
this
.
preview
.
shown
=
!
this
.
preview
?.
shown
;
},
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_webide_ext.js
View file @
021bb329
/**
* A WebIDE Extension options for Source Editor
* @typedef {Object} WebIDEExtensionOptions
* @property {Object} modelManager The root manager for WebIDE models
* @property {Object} store The state store for communication
* @property {Object} file
* @property {Object} options The Monaco editor options
*/
import
{
debounce
}
from
'
lodash
'
;
import
{
debounce
}
from
'
lodash
'
;
import
{
KeyCode
,
KeyMod
,
Range
}
from
'
monaco-editor
'
;
import
{
KeyCode
,
KeyMod
,
Range
}
from
'
monaco-editor
'
;
import
{
EDITOR_TYPE_DIFF
}
from
'
~/editor/constants
'
;
import
{
EDITOR_TYPE_DIFF
}
from
'
~/editor/constants
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
Disposable
from
'
~/ide/lib/common/disposable
'
;
import
Disposable
from
'
~/ide/lib/common/disposable
'
;
import
{
editorOptions
}
from
'
~/ide/lib/editor_options
'
;
import
{
editorOptions
}
from
'
~/ide/lib/editor_options
'
;
import
keymap
from
'
~/ide/lib/keymap.json
'
;
import
keymap
from
'
~/ide/lib/keymap.json
'
;
...
@@ -11,154 +19,168 @@ const isDiffEditorType = (instance) => {
...
@@ -11,154 +19,168 @@ const isDiffEditorType = (instance) => {
};
};
export
const
UPDATE_DIMENSIONS_DELAY
=
200
;
export
const
UPDATE_DIMENSIONS_DELAY
=
200
;
const
defaultOptions
=
{
modelManager
:
undefined
,
store
:
undefined
,
file
:
undefined
,
options
:
{},
};
export
class
EditorWebIdeExtension
extends
SourceEditorExtension
{
const
addActions
=
(
instance
,
store
)
=>
{
constructor
({
instance
,
modelManager
,
...
options
}
=
{})
{
const
getKeyCode
=
(
key
)
=>
{
super
({
const
monacoKeyMod
=
key
.
indexOf
(
'
KEY_
'
)
===
0
;
instance
,
...
options
,
modelManager
,
disposable
:
new
Disposable
(),
debouncedUpdate
:
debounce
(()
=>
{
instance
.
updateDimensions
();
},
UPDATE_DIMENSIONS_DELAY
),
});
window
.
addEventListener
(
'
resize
'
,
instance
.
debouncedUpdate
,
false
);
instance
.
onDidDispose
(()
=>
{
window
.
removeEventListener
(
'
resize
'
,
instance
.
debouncedUpdate
);
// catch any potential errors with disposing the error
// this is mainly for tests caused by elements not existing
try
{
instance
.
disposable
.
dispose
();
}
catch
(
e
)
{
if
(
process
.
env
.
NODE_ENV
!==
'
test
'
)
{
// eslint-disable-next-line no-console
console
.
error
(
e
);
}
}
});
EditorWebIdeExtension
.
addActions
(
instance
)
;
return
monacoKeyMod
?
KeyCode
[
key
]
:
KeyMod
[
key
]
;
}
}
;
static
addActions
(
instance
)
{
keymap
.
forEach
((
command
)
=>
{
const
{
store
}
=
instance
;
const
{
bindings
,
id
,
label
,
action
}
=
command
;
const
getKeyCode
=
(
key
)
=>
{
const
monacoKeyMod
=
key
.
indexOf
(
'
KEY_
'
)
===
0
;
return
monacoKeyMod
?
KeyCode
[
key
]
:
KeyMod
[
key
];
const
keybindings
=
bindings
.
map
((
binding
)
=>
{
}
;
const
keys
=
binding
.
split
(
'
+
'
)
;
keymap
.
forEach
((
command
)
=>
{
// eslint-disable-next-line no-bitwise
const
{
bindings
,
id
,
label
,
action
}
=
command
;
return
keys
.
length
>
1
?
getKeyCode
(
keys
[
0
])
|
getKeyCode
(
keys
[
1
])
:
getKeyCode
(
keys
[
0
]);
const
keybindings
=
bindings
.
map
((
binding
)
=>
{
const
keys
=
binding
.
split
(
'
+
'
);
// eslint-disable-next-line no-bitwise
return
keys
.
length
>
1
?
getKeyCode
(
keys
[
0
])
|
getKeyCode
(
keys
[
1
])
:
getKeyCode
(
keys
[
0
]);
});
instance
.
addAction
({
id
,
label
,
keybindings
,
run
()
{
store
.
dispatch
(
action
.
name
,
action
.
params
);
return
null
;
},
});
});
});
}
createModel
(
file
,
head
=
null
)
{
return
this
.
modelManager
.
addModel
(
file
,
head
);
}
attachModel
(
model
)
{
if
(
isDiffEditorType
(
this
))
{
this
.
setModel
({
original
:
model
.
getOriginalModel
(),
modified
:
model
.
getModel
(),
});
return
;
instance
.
addAction
({
}
id
,
label
,
this
.
setModel
(
model
.
getModel
());
keybindings
,
run
()
{
store
.
dispatch
(
action
.
name
,
action
.
params
);
return
null
;
},
});
});
};
this
.
updateOptions
(
const
renderSideBySide
=
(
domElement
)
=>
{
editorOptions
.
reduce
((
acc
,
obj
)
=>
{
return
domElement
.
offsetWidth
>=
700
;
Object
.
keys
(
obj
).
forEach
((
key
)
=>
{
};
Object
.
assign
(
acc
,
{
[
key
]:
obj
[
key
](
model
),
});
});
return
acc
;
},
{}),
);
}
attachMergeRequestModel
(
model
)
{
const
updateInstanceDimensions
=
(
instance
)
=>
{
this
.
setModel
({
instance
.
layout
();
original
:
model
.
getBaseModel
(),
if
(
isDiffEditorType
(
instance
))
{
modified
:
model
.
getModel
(),
instance
.
updateOptions
({
renderSideBySide
:
renderSideBySide
(
instance
.
getDomNode
()),
});
});
}
}
};
updateDimensions
()
{
export
class
EditorWebIdeExtension
{
this
.
layout
();
static
get
extensionName
()
{
this
.
updateDiffView
()
;
return
'
EditorWebIde
'
;
}
}
setPos
({
lineNumber
,
column
})
{
/**
this
.
revealPositionInCenter
({
* Set up the WebIDE extension for Source Editor
lineNumber
,
* @param {module:source_editor_instance~EditorInstance} instance - The Source Editor instance
column
,
* @param {WebIDEExtensionOptions} setupOptions
});
*/
this
.
setPosition
({
onSetup
(
instance
,
setupOptions
=
defaultOptions
)
{
lineNumber
,
this
.
modelManager
=
setupOptions
.
modelManager
;
column
,
this
.
store
=
setupOptions
.
store
;
});
this
.
file
=
setupOptions
.
file
;
this
.
options
=
setupOptions
.
options
;
this
.
disposable
=
new
Disposable
();
this
.
debouncedUpdate
=
debounce
(()
=>
{
updateInstanceDimensions
(
instance
);
},
UPDATE_DIMENSIONS_DELAY
);
addActions
(
instance
,
setupOptions
.
store
);
}
}
onPositionChange
(
cb
)
{
onUse
(
instance
)
{
if
(
!
this
.
onDidChangeCursorPosition
)
{
window
.
addEventListener
(
'
resize
'
,
this
.
debouncedUpdate
,
false
);
return
;
}
this
.
disposable
.
add
(
this
.
onDidChangeCursorPosition
((
e
)
=>
cb
(
this
,
e
)));
instance
.
onDidDispose
(()
=>
{
this
.
onUnuse
();
});
}
}
updateDiffView
()
{
onUnuse
()
{
if
(
!
isDiffEditorType
(
this
))
{
window
.
removeEventListener
(
'
resize
'
,
this
.
debouncedUpdate
);
return
;
// catch any potential errors with disposing the error
// this is mainly for tests caused by elements not existing
try
{
this
.
disposable
.
dispose
();
}
catch
(
e
)
{
if
(
process
.
env
.
NODE_ENV
!==
'
test
'
)
{
// eslint-disable-next-line no-console
console
.
error
(
e
);
}
}
}
this
.
updateOptions
({
renderSideBySide
:
EditorWebIdeExtension
.
renderSideBySide
(
this
.
getDomNode
()),
});
}
}
replaceSelectedText
(
text
)
{
provides
()
{
let
selection
=
this
.
getSelection
();
return
{
const
range
=
new
Range
(
createModel
:
(
instance
,
file
,
head
=
null
)
=>
{
selection
.
startLineNumber
,
return
this
.
modelManager
.
addModel
(
file
,
head
);
selection
.
startColumn
,
},
selection
.
endLineNumber
,
attachModel
:
(
instance
,
model
)
=>
{
selection
.
endColumn
,
if
(
isDiffEditorType
(
instance
))
{
);
instance
.
setModel
({
original
:
model
.
getOriginalModel
(),
modified
:
model
.
getModel
(),
});
this
.
executeEdits
(
''
,
[{
range
,
text
}]);
return
;
}
selection
=
this
.
getSelection
();
instance
.
setModel
(
model
.
getModel
());
this
.
setPosition
({
lineNumber
:
selection
.
endLineNumber
,
column
:
selection
.
endColumn
});
}
instance
.
updateOptions
(
editorOptions
.
reduce
((
acc
,
obj
)
=>
{
Object
.
keys
(
obj
).
forEach
((
key
)
=>
{
Object
.
assign
(
acc
,
{
[
key
]:
obj
[
key
](
model
),
});
});
return
acc
;
},
{}),
);
},
attachMergeRequestModel
:
(
instance
,
model
)
=>
{
instance
.
setModel
({
original
:
model
.
getBaseModel
(),
modified
:
model
.
getModel
(),
});
},
updateDimensions
:
(
instance
)
=>
updateInstanceDimensions
(
instance
),
setPos
:
(
instance
,
{
lineNumber
,
column
})
=>
{
instance
.
revealPositionInCenter
({
lineNumber
,
column
,
});
instance
.
setPosition
({
lineNumber
,
column
,
});
},
onPositionChange
:
(
instance
,
cb
)
=>
{
if
(
typeof
instance
.
onDidChangeCursorPosition
!==
'
function
'
)
{
return
;
}
static
renderSideBySide
(
domElement
)
{
this
.
disposable
.
add
(
instance
.
onDidChangeCursorPosition
((
e
)
=>
cb
(
instance
,
e
)));
return
domElement
.
offsetWidth
>=
700
;
},
replaceSelectedText
:
(
instance
,
text
)
=>
{
let
selection
=
instance
.
getSelection
();
const
range
=
new
Range
(
selection
.
startLineNumber
,
selection
.
startColumn
,
selection
.
endLineNumber
,
selection
.
endColumn
,
);
instance
.
executeEdits
(
''
,
[{
range
,
text
}]);
selection
=
instance
.
getSelection
();
instance
.
setPosition
({
lineNumber
:
selection
.
endLineNumber
,
column
:
selection
.
endColumn
});
},
};
}
}
}
}
app/assets/javascripts/editor/extensions/source_editor_yaml_ext.js
View file @
021bb329
This diff is collapsed.
Click to expand it.
app/assets/javascripts/editor/source_editor.js
View file @
021bb329
import
{
editor
as
monacoEditor
,
Uri
}
from
'
monaco-editor
'
;
import
{
editor
as
monacoEditor
,
Uri
}
from
'
monaco-editor
'
;
import
{
waitForCSSLoaded
}
from
'
~/helpers/startup_css_helper
'
;
import
{
defaultEditorOptions
}
from
'
~/ide/lib/editor_options
'
;
import
{
defaultEditorOptions
}
from
'
~/ide/lib/editor_options
'
;
import
languages
from
'
~/ide/lib/languages
'
;
import
languages
from
'
~/ide/lib/languages
'
;
import
{
registerLanguages
}
from
'
~/ide/utils
'
;
import
{
registerLanguages
}
from
'
~/ide/utils
'
;
...
@@ -11,10 +12,39 @@ import {
...
@@ -11,10 +12,39 @@ import {
EDITOR_TYPE_DIFF
,
EDITOR_TYPE_DIFF
,
}
from
'
./constants
'
;
}
from
'
./constants
'
;
import
{
clearDomElement
,
setupEditorTheme
,
getBlobLanguage
}
from
'
./utils
'
;
import
{
clearDomElement
,
setupEditorTheme
,
getBlobLanguage
}
from
'
./utils
'
;
import
EditorInstance
from
'
./source_editor_instance
'
;
const
instanceRemoveFromRegistry
=
(
editor
,
instance
)
=>
{
const
index
=
editor
.
instances
.
findIndex
((
inst
)
=>
inst
===
instance
);
editor
.
instances
.
splice
(
index
,
1
);
};
const
instanceDisposeModels
=
(
editor
,
instance
,
model
)
=>
{
const
instanceModel
=
instance
.
getModel
()
||
model
;
if
(
!
instanceModel
)
{
return
;
}
if
(
instance
.
getEditorType
()
===
EDITOR_TYPE_DIFF
)
{
const
{
original
,
modified
}
=
instanceModel
;
if
(
original
)
{
original
.
dispose
();
}
if
(
modified
)
{
modified
.
dispose
();
}
}
else
{
instanceModel
.
dispose
();
}
};
export
default
class
SourceEditor
{
export
default
class
SourceEditor
{
/**
* Constructs a global editor.
* @param {Object} options - Monaco config options used to create the editor
*/
constructor
(
options
=
{})
{
constructor
(
options
=
{})
{
this
.
instances
=
[];
this
.
instances
=
[];
this
.
extensionsStore
=
new
Map
();
this
.
options
=
{
this
.
options
=
{
extraEditorClassName
:
'
gl-source-editor
'
,
extraEditorClassName
:
'
gl-source-editor
'
,
...
defaultEditorOptions
,
...
defaultEditorOptions
,
...
@@ -26,19 +56,6 @@ export default class SourceEditor {
...
@@ -26,19 +56,6 @@ export default class SourceEditor {
registerLanguages
(...
languages
);
registerLanguages
(...
languages
);
}
}
static
mixIntoInstance
(
source
,
inst
)
{
if
(
!
inst
)
{
return
;
}
const
isClassInstance
=
source
.
constructor
.
prototype
!==
Object
.
prototype
;
const
sanitizedSource
=
isClassInstance
?
source
.
constructor
.
prototype
:
source
;
Object
.
getOwnPropertyNames
(
sanitizedSource
).
forEach
((
prop
)
=>
{
if
(
prop
!==
'
constructor
'
)
{
Object
.
assign
(
inst
,
{
[
prop
]:
source
[
prop
]
});
}
});
}
static
prepareInstance
(
el
)
{
static
prepareInstance
(
el
)
{
if
(
!
el
)
{
if
(
!
el
)
{
throw
new
Error
(
SOURCE_EDITOR_INSTANCE_ERROR_NO_EL
);
throw
new
Error
(
SOURCE_EDITOR_INSTANCE_ERROR_NO_EL
);
...
@@ -78,71 +95,17 @@ export default class SourceEditor {
...
@@ -78,71 +95,17 @@ export default class SourceEditor {
return
diffModel
;
return
diffModel
;
}
}
static
convertMonacoToELInstance
=
(
inst
)
=>
{
const
sourceEditorInstanceAPI
=
{
updateModelLanguage
:
(
path
)
=>
{
return
SourceEditor
.
instanceUpdateLanguage
(
inst
,
path
);
},
use
:
(
exts
=
[])
=>
{
return
SourceEditor
.
instanceApplyExtension
(
inst
,
exts
);
},
};
const
handler
=
{
get
(
target
,
prop
,
receiver
)
{
if
(
Reflect
.
has
(
sourceEditorInstanceAPI
,
prop
))
{
return
sourceEditorInstanceAPI
[
prop
];
}
return
Reflect
.
get
(
target
,
prop
,
receiver
);
},
};
return
new
Proxy
(
inst
,
handler
);
};
static
instanceUpdateLanguage
(
inst
,
path
)
{
const
lang
=
getBlobLanguage
(
path
);
const
model
=
inst
.
getModel
();
return
monacoEditor
.
setModelLanguage
(
model
,
lang
);
}
static
instanceApplyExtension
(
inst
,
exts
=
[])
{
const
extensions
=
[].
concat
(
exts
);
extensions
.
forEach
((
extension
)
=>
{
SourceEditor
.
mixIntoInstance
(
extension
,
inst
);
});
return
inst
;
}
static
instanceRemoveFromRegistry
(
editor
,
instance
)
{
const
index
=
editor
.
instances
.
findIndex
((
inst
)
=>
inst
===
instance
);
editor
.
instances
.
splice
(
index
,
1
);
}
static
instanceDisposeModels
(
editor
,
instance
,
model
)
{
const
instanceModel
=
instance
.
getModel
()
||
model
;
if
(
!
instanceModel
)
{
return
;
}
if
(
instance
.
getEditorType
()
===
EDITOR_TYPE_DIFF
)
{
const
{
original
,
modified
}
=
instanceModel
;
if
(
original
)
{
original
.
dispose
();
}
if
(
modified
)
{
modified
.
dispose
();
}
}
else
{
instanceModel
.
dispose
();
}
}
/**
/**
* Creates a monaco instance with the given options.
* Creates a Source Editor Instance with the given options.
*
* @param {Object} options Options used to initialize the instance.
* @param {Object} options Options used to initialize monaco.
* @param {Element} options.el The element to attach the instance for.
* @param {Element} options.el The element which will be used to create the monacoEditor.
* @param {string} options.blobPath The path used as the URI of the model. Monaco uses the extension of this path to determine the language.
* @param {string} options.blobPath The path used as the URI of the model. Monaco uses the extension of this path to determine the language.
* @param {string} options.blobContent The content to initialize the monacoEditor.
* @param {string} options.blobContent The content to initialize the monacoEditor.
* @param {string} options.blobOriginalContent The original blob's content. Is used when creating a Diff Instance.
* @param {string} options.blobGlobalId This is used to help globally identify monaco instances that are created with the same blobPath.
* @param {string} options.blobGlobalId This is used to help globally identify monaco instances that are created with the same blobPath.
* @param {Boolean} options.isDiff Flag to enable creation of a Diff Instance?
* @param {...*} options.instanceOptions Configuration options used to instantiate an instance.
* @returns {EditorInstance}
*/
*/
createInstance
({
createInstance
({
el
=
undefined
,
el
=
undefined
,
...
@@ -156,13 +119,18 @@ export default class SourceEditor {
...
@@ -156,13 +119,18 @@ export default class SourceEditor {
SourceEditor
.
prepareInstance
(
el
);
SourceEditor
.
prepareInstance
(
el
);
const
createEditorFn
=
isDiff
?
'
createDiffEditor
'
:
'
create
'
;
const
createEditorFn
=
isDiff
?
'
createDiffEditor
'
:
'
create
'
;
const
instance
=
SourceEditor
.
convertMonacoToEL
Instance
(
const
instance
=
new
Editor
Instance
(
monacoEditor
[
createEditorFn
].
call
(
this
,
el
,
{
monacoEditor
[
createEditorFn
].
call
(
this
,
el
,
{
...
this
.
options
,
...
this
.
options
,
...
instanceOptions
,
...
instanceOptions
,
}),
}),
this
.
extensionsStore
,
);
);
waitForCSSLoaded
(()
=>
{
instance
.
layout
();
});
let
model
;
let
model
;
if
(
instanceOptions
.
model
!==
null
)
{
if
(
instanceOptions
.
model
!==
null
)
{
model
=
SourceEditor
.
createEditorModel
({
model
=
SourceEditor
.
createEditorModel
({
...
@@ -176,8 +144,8 @@ export default class SourceEditor {
...
@@ -176,8 +144,8 @@ export default class SourceEditor {
}
}
instance
.
onDidDispose
(()
=>
{
instance
.
onDidDispose
(()
=>
{
SourceEditor
.
instanceRemoveFromRegistry
(
this
,
instance
);
instanceRemoveFromRegistry
(
this
,
instance
);
SourceEditor
.
instanceDisposeModels
(
this
,
instance
,
model
);
instanceDisposeModels
(
this
,
instance
,
model
);
});
});
this
.
instances
.
push
(
instance
);
this
.
instances
.
push
(
instance
);
...
@@ -185,6 +153,11 @@ export default class SourceEditor {
...
@@ -185,6 +153,11 @@ export default class SourceEditor {
return
instance
;
return
instance
;
}
}
/**
* Create a Diff Instance
* @param {Object} args Options to be passed further down to createInstance() with the same signature
* @returns {EditorInstance}
*/
createDiffInstance
(
args
)
{
createDiffInstance
(
args
)
{
return
this
.
createInstance
({
return
this
.
createInstance
({
...
args
,
...
args
,
...
@@ -192,6 +165,10 @@ export default class SourceEditor {
...
@@ -192,6 +165,10 @@ export default class SourceEditor {
});
});
}
}
/**
* Dispose global editor
* Automatically disposes all the instances registered for this editor
*/
dispose
()
{
dispose
()
{
this
.
instances
.
forEach
((
instance
)
=>
instance
.
dispose
());
this
.
instances
.
forEach
((
instance
)
=>
instance
.
dispose
());
}
}
...
...
app/assets/javascripts/editor/source_editor_extension.js
View file @
021bb329
...
@@ -5,10 +5,10 @@ export default class EditorExtension {
...
@@ -5,10 +5,10 @@ export default class EditorExtension {
if
(
typeof
definition
!==
'
function
'
)
{
if
(
typeof
definition
!==
'
function
'
)
{
throw
new
Error
(
EDITOR_EXTENSION_DEFINITION_ERROR
);
throw
new
Error
(
EDITOR_EXTENSION_DEFINITION_ERROR
);
}
}
this
.
name
=
definition
.
name
;
// both class- and fn-based extensions have a name
this
.
setupOptions
=
setupOptions
;
this
.
setupOptions
=
setupOptions
;
// eslint-disable-next-line new-cap
// eslint-disable-next-line new-cap
this
.
obj
=
new
definition
();
this
.
obj
=
new
definition
();
this
.
extensionName
=
definition
.
extensionName
||
this
.
obj
.
extensionName
;
// both class- and fn-based extensions have a name
}
}
get
api
()
{
get
api
()
{
...
...
app/assets/javascripts/editor/source_editor_instance.js
View file @
021bb329
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
* A Source Editor Extension
* A Source Editor Extension
* @typedef {Object} SourceEditorExtension
* @typedef {Object} SourceEditorExtension
* @property {Object} obj
* @property {Object} obj
* @property {string}
n
ame
* @property {string}
extensionN
ame
* @property {Object} api
* @property {Object} api
*/
*/
...
@@ -43,12 +43,12 @@ const utils = {
...
@@ -43,12 +43,12 @@ const utils = {
}
}
},
},
getStoredExtension
:
(
extensionsStore
,
n
ame
)
=>
{
getStoredExtension
:
(
extensionsStore
,
extensionN
ame
)
=>
{
if
(
!
extensionsStore
)
{
if
(
!
extensionsStore
)
{
logError
(
EDITOR_EXTENSION_STORE_IS_MISSING_ERROR
);
logError
(
EDITOR_EXTENSION_STORE_IS_MISSING_ERROR
);
return
undefined
;
return
undefined
;
}
}
return
extensionsStore
.
get
(
n
ame
);
return
extensionsStore
.
get
(
extensionN
ame
);
},
},
};
};
...
@@ -73,30 +73,18 @@ export default class EditorInstance {
...
@@ -73,30 +73,18 @@ export default class EditorInstance {
if
(
methodExtension
)
{
if
(
methodExtension
)
{
const
extension
=
extensionsStore
.
get
(
methodExtension
);
const
extension
=
extensionsStore
.
get
(
methodExtension
);
return
(...
args
)
=>
extension
.
api
[
prop
].
call
(
seInstance
,
receiver
,
...
args
);
if
(
typeof
extension
.
api
[
prop
]
===
'
function
'
)
{
return
extension
.
api
[
prop
].
bind
(
extension
.
obj
,
receiver
);
}
return
extension
.
api
[
prop
];
}
}
return
Reflect
.
get
(
seInstance
[
prop
]
?
seInstance
:
target
,
prop
,
receiver
);
return
Reflect
.
get
(
seInstance
[
prop
]
?
seInstance
:
target
,
prop
,
receiver
);
},
},
set
(
target
,
prop
,
value
)
{
Object
.
assign
(
seInstance
,
{
[
prop
]:
value
,
});
return
true
;
},
};
};
const
instProxy
=
new
Proxy
(
rootInstance
,
getHandler
);
const
instProxy
=
new
Proxy
(
rootInstance
,
getHandler
);
/**
this
.
dispatchExtAction
=
EditorInstance
.
useUnuse
.
bind
(
instProxy
,
extensionsStore
);
* Main entry point to apply an extension to the instance
* @param {SourceEditorExtensionDefinition}
*/
this
.
use
=
EditorInstance
.
useUnuse
.
bind
(
instProxy
,
extensionsStore
,
this
.
useExtension
);
/**
* Main entry point to un-use an extension and remove it from the instance
* @param {SourceEditorExtension}
*/
this
.
unuse
=
EditorInstance
.
useUnuse
.
bind
(
instProxy
,
extensionsStore
,
this
.
unuseExtension
);
return
instProxy
;
return
instProxy
;
}
}
...
@@ -141,7 +129,7 @@ export default class EditorInstance {
...
@@ -141,7 +129,7 @@ export default class EditorInstance {
}
}
// Existing Extension Path
// Existing Extension Path
const
existingExt
=
utils
.
getStoredExtension
(
extensionsStore
,
definition
.
n
ame
);
const
existingExt
=
utils
.
getStoredExtension
(
extensionsStore
,
definition
.
extensionN
ame
);
if
(
existingExt
)
{
if
(
existingExt
)
{
if
(
isEqual
(
extension
.
setupOptions
,
existingExt
.
setupOptions
))
{
if
(
isEqual
(
extension
.
setupOptions
,
existingExt
.
setupOptions
))
{
return
existingExt
;
return
existingExt
;
...
@@ -168,14 +156,14 @@ export default class EditorInstance {
...
@@ -168,14 +156,14 @@ export default class EditorInstance {
* @param {Map} extensionsStore - The global registry for the extension instances
* @param {Map} extensionsStore - The global registry for the extension instances
*/
*/
registerExtension
(
extension
,
extensionsStore
)
{
registerExtension
(
extension
,
extensionsStore
)
{
const
{
n
ame
}
=
extension
;
const
{
extensionN
ame
}
=
extension
;
const
hasExtensionRegistered
=
const
hasExtensionRegistered
=
extensionsStore
.
has
(
n
ame
)
&&
extensionsStore
.
has
(
extensionN
ame
)
&&
isEqual
(
extension
.
setupOptions
,
extensionsStore
.
get
(
n
ame
).
setupOptions
);
isEqual
(
extension
.
setupOptions
,
extensionsStore
.
get
(
extensionN
ame
).
setupOptions
);
if
(
hasExtensionRegistered
)
{
if
(
hasExtensionRegistered
)
{
return
;
return
;
}
}
extensionsStore
.
set
(
n
ame
,
extension
);
extensionsStore
.
set
(
extensionN
ame
,
extension
);
const
{
obj
:
extensionObj
}
=
extension
;
const
{
obj
:
extensionObj
}
=
extension
;
if
(
extensionObj
.
onUse
)
{
if
(
extensionObj
.
onUse
)
{
extensionObj
.
onUse
(
this
);
extensionObj
.
onUse
(
this
);
...
@@ -187,7 +175,7 @@ export default class EditorInstance {
...
@@ -187,7 +175,7 @@ export default class EditorInstance {
* @param {SourceEditorExtension} extension - Instance of Source Editor extension
* @param {SourceEditorExtension} extension - Instance of Source Editor extension
*/
*/
registerExtensionMethods
(
extension
)
{
registerExtensionMethods
(
extension
)
{
const
{
api
,
n
ame
}
=
extension
;
const
{
api
,
extensionN
ame
}
=
extension
;
if
(
!
api
)
{
if
(
!
api
)
{
return
;
return
;
...
@@ -197,7 +185,7 @@ export default class EditorInstance {
...
@@ -197,7 +185,7 @@ export default class EditorInstance {
if
(
this
[
prop
])
{
if
(
this
[
prop
])
{
logError
(
sprintf
(
EDITOR_EXTENSION_NAMING_CONFLICT_ERROR
,
{
prop
}));
logError
(
sprintf
(
EDITOR_EXTENSION_NAMING_CONFLICT_ERROR
,
{
prop
}));
}
else
{
}
else
{
this
.
methods
[
prop
]
=
n
ame
;
this
.
methods
[
prop
]
=
extensionN
ame
;
}
}
},
this
);
},
this
);
}
}
...
@@ -215,10 +203,10 @@ export default class EditorInstance {
...
@@ -215,10 +203,10 @@ export default class EditorInstance {
if
(
!
extension
)
{
if
(
!
extension
)
{
throw
new
Error
(
EDITOR_EXTENSION_NOT_SPECIFIED_FOR_UNUSE_ERROR
);
throw
new
Error
(
EDITOR_EXTENSION_NOT_SPECIFIED_FOR_UNUSE_ERROR
);
}
}
const
{
n
ame
}
=
extension
;
const
{
extensionN
ame
}
=
extension
;
const
existingExt
=
utils
.
getStoredExtension
(
extensionsStore
,
n
ame
);
const
existingExt
=
utils
.
getStoredExtension
(
extensionsStore
,
extensionN
ame
);
if
(
!
existingExt
)
{
if
(
!
existingExt
)
{
throw
new
Error
(
sprintf
(
EDITOR_EXTENSION_NOT_REGISTERED_ERROR
,
{
n
ame
}));
throw
new
Error
(
sprintf
(
EDITOR_EXTENSION_NOT_REGISTERED_ERROR
,
{
extensionN
ame
}));
}
}
const
{
obj
:
extensionObj
}
=
existingExt
;
const
{
obj
:
extensionObj
}
=
existingExt
;
if
(
extensionObj
.
onBeforeUnuse
)
{
if
(
extensionObj
.
onBeforeUnuse
)
{
...
@@ -235,12 +223,12 @@ export default class EditorInstance {
...
@@ -235,12 +223,12 @@ export default class EditorInstance {
* @param {SourceEditorExtension} extension - Instance of Source Editor extension to un-use
* @param {SourceEditorExtension} extension - Instance of Source Editor extension to un-use
*/
*/
unregisterExtensionMethods
(
extension
)
{
unregisterExtensionMethods
(
extension
)
{
const
{
api
,
n
ame
}
=
extension
;
const
{
api
,
extensionN
ame
}
=
extension
;
if
(
!
api
)
{
if
(
!
api
)
{
return
;
return
;
}
}
Object
.
keys
(
api
).
forEach
((
method
)
=>
{
Object
.
keys
(
api
).
forEach
((
method
)
=>
{
utils
.
removeExtFromMethod
(
method
,
n
ame
,
this
.
methods
);
utils
.
removeExtFromMethod
(
method
,
extensionN
ame
,
this
.
methods
);
});
});
}
}
...
@@ -259,6 +247,24 @@ export default class EditorInstance {
...
@@ -259,6 +247,24 @@ export default class EditorInstance {
monacoEditor
.
setModelLanguage
(
model
,
lang
);
monacoEditor
.
setModelLanguage
(
model
,
lang
);
}
}
/**
* Main entry point to apply an extension to the instance
* @param {SourceEditorExtensionDefinition[]|SourceEditorExtensionDefinition} extDefs - The extension(s) to use
* @returns {EditorExtension|*}
*/
use
(
extDefs
)
{
return
this
.
dispatchExtAction
(
this
.
useExtension
,
extDefs
);
}
/**
* Main entry point to remove an extension to the instance
* @param {SourceEditorExtension[]|SourceEditorExtension} exts -
* @returns {*}
*/
unuse
(
exts
)
{
return
this
.
dispatchExtAction
(
this
.
unuseExtension
,
exts
);
}
/**
/**
* Get the methods returned by extensions.
* Get the methods returned by extensions.
* @returns {Array}
* @returns {Array}
...
...
app/assets/javascripts/ide/components/repo_editor.vue
View file @
021bb329
...
@@ -7,6 +7,7 @@ import {
...
@@ -7,6 +7,7 @@ import {
EDITOR_CODE_INSTANCE_FN
,
EDITOR_CODE_INSTANCE_FN
,
EDITOR_DIFF_INSTANCE_FN
,
EDITOR_DIFF_INSTANCE_FN
,
}
from
'
~/editor/constants
'
;
}
from
'
~/editor/constants
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
{
EditorWebIdeExtension
}
from
'
~/editor/extensions/source_editor_webide_ext
'
;
import
{
EditorWebIdeExtension
}
from
'
~/editor/extensions/source_editor_webide_ext
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
...
@@ -302,30 +303,32 @@ export default {
...
@@ -302,30 +303,32 @@ export default {
...
instanceOptions
,
...
instanceOptions
,
...
this
.
editorOptions
,
...
this
.
editorOptions
,
});
});
this
.
editor
.
use
([
this
.
editor
.
use
(
{
new
EditorWebIdeExtension
({
definition
:
SourceEditorExtension
,
instance
:
this
.
editor
,
},
modelManager
:
this
.
modelManager
,
{
store
:
this
.
$store
,
definition
:
EditorWebIdeExtension
,
file
:
this
.
file
,
setupOptions
:
{
options
:
this
.
editorOptions
,
modelManager
:
this
.
modelManager
,
}),
store
:
this
.
$store
,
);
file
:
this
.
file
,
options
:
this
.
editorOptions
,
},
},
]);
if
(
if
(
this
.
fileType
===
MARKDOWN_FILE_TYPE
&&
this
.
fileType
===
MARKDOWN_FILE_TYPE
&&
this
.
editor
?.
getEditorType
()
===
EDITOR_TYPE_CODE
&&
this
.
editor
?.
getEditorType
()
===
EDITOR_TYPE_CODE
&&
this
.
previewMarkdownPath
this
.
previewMarkdownPath
)
{
)
{
import
(
'
~/editor/extensions/source_editor_markdown_ext
'
)
import
(
'
~/editor/extensions/source_editor_markdown_livepreview_ext
'
)
.
then
(({
EditorMarkdownExtension
:
MarkdownExtension
}
=
{})
=>
{
.
then
(({
EditorMarkdownPreviewExtension
:
MarkdownLivePreview
})
=>
{
this
.
editor
.
use
(
this
.
editor
.
use
({
new
MarkdownExtension
({
definition
:
MarkdownLivePreview
,
instance
:
this
.
editor
,
setupOptions
:
{
previewMarkdownPath
:
this
.
previewMarkdownPath
},
previewMarkdownPath
:
this
.
previewMarkdownPath
,
});
}),
);
})
})
.
catch
((
e
)
=>
.
catch
((
e
)
=>
createFlash
({
createFlash
({
...
...
app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue
View file @
021bb329
...
@@ -19,7 +19,7 @@ export default {
...
@@ -19,7 +19,7 @@ export default {
if
(
this
.
glFeatures
.
schemaLinting
)
{
if
(
this
.
glFeatures
.
schemaLinting
)
{
const
editorInstance
=
this
.
$refs
.
editor
.
getEditor
();
const
editorInstance
=
this
.
$refs
.
editor
.
getEditor
();
editorInstance
.
use
(
new
CiSchemaExtension
({
instance
:
editorInstance
})
);
editorInstance
.
use
(
{
definition
:
CiSchemaExtension
}
);
editorInstance
.
registerCiSchema
();
editorInstance
.
registerCiSchema
();
}
}
},
},
...
...
spec/frontend/blob_edit/edit_blob_spec.js
View file @
021bb329
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
EditBlob
from
'
~/blob_edit/edit_blob
'
;
import
EditBlob
from
'
~/blob_edit/edit_blob
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
{
FileTemplateExtension
}
from
'
~/editor/extensions/source_editor_file_template_ext
'
;
import
{
FileTemplateExtension
}
from
'
~/editor/extensions/source_editor_file_template_ext
'
;
import
{
EditorMarkdownExtension
}
from
'
~/editor/extensions/source_editor_markdown_ext
'
;
import
{
EditorMarkdownExtension
}
from
'
~/editor/extensions/source_editor_markdown_ext
'
;
import
{
EditorMarkdownPreviewExtension
}
from
'
~/editor/extensions/source_editor_markdown_livepreview_ext
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
jest
.
mock
(
'
~/editor/source_editor
'
);
jest
.
mock
(
'
~/editor/source_editor
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_
markdown_ext
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_
extension_base
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_file_template_ext
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_file_template_ext
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_markdown_ext
'
);
jest
.
mock
(
'
~/editor/extensions/source_editor_markdown_livepreview_ext
'
);
const
PREVIEW_MARKDOWN_PATH
=
'
/foo/bar/preview_markdown
'
;
const
PREVIEW_MARKDOWN_PATH
=
'
/foo/bar/preview_markdown
'
;
const
defaultExtensions
=
[
{
definition
:
SourceEditorExtension
},
{
definition
:
FileTemplateExtension
},
];
const
markdownExtensions
=
[
{
definition
:
EditorMarkdownExtension
},
{
definition
:
EditorMarkdownPreviewExtension
,
setupOptions
:
{
previewMarkdownPath
:
PREVIEW_MARKDOWN_PATH
},
},
];
describe
(
'
Blob Editing
'
,
()
=>
{
describe
(
'
Blob Editing
'
,
()
=>
{
const
useMock
=
jest
.
fn
();
const
useMock
=
jest
.
fn
();
...
@@ -29,7 +44,9 @@ describe('Blob Editing', () => {
...
@@ -29,7 +44,9 @@ describe('Blob Editing', () => {
jest
.
spyOn
(
SourceEditor
.
prototype
,
'
createInstance
'
).
mockReturnValue
(
mockInstance
);
jest
.
spyOn
(
SourceEditor
.
prototype
,
'
createInstance
'
).
mockReturnValue
(
mockInstance
);
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
SourceEditorExtension
.
mockClear
();
EditorMarkdownExtension
.
mockClear
();
EditorMarkdownExtension
.
mockClear
();
EditorMarkdownPreviewExtension
.
mockClear
();
FileTemplateExtension
.
mockClear
();
FileTemplateExtension
.
mockClear
();
});
});
...
@@ -45,26 +62,22 @@ describe('Blob Editing', () => {
...
@@ -45,26 +62,22 @@ describe('Blob Editing', () => {
await
waitForPromises
();
await
waitForPromises
();
};
};
it
(
'
loads FileTemplateExtension by default
'
,
async
()
=>
{
it
(
'
loads
SourceEditorExtension and
FileTemplateExtension by default
'
,
async
()
=>
{
await
initEditor
();
await
initEditor
();
expect
(
useMock
).
toHaveBeenCalledWith
(
expect
.
any
(
FileTemplateExtension
));
expect
(
useMock
).
toHaveBeenCalledWith
(
defaultExtensions
);
expect
(
FileTemplateExtension
).
toHaveBeenCalledTimes
(
1
);
});
});
describe
(
'
Markdown
'
,
()
=>
{
describe
(
'
Markdown
'
,
()
=>
{
it
(
'
does not load MarkdownExtension by default
'
,
async
()
=>
{
it
(
'
does not load MarkdownExtension
s
by default
'
,
async
()
=>
{
await
initEditor
();
await
initEditor
();
expect
(
EditorMarkdownExtension
).
not
.
toHaveBeenCalled
();
expect
(
EditorMarkdownExtension
).
not
.
toHaveBeenCalled
();
expect
(
EditorMarkdownPreviewExtension
).
not
.
toHaveBeenCalled
();
});
});
it
(
'
loads MarkdownExtension only for the markdown files
'
,
async
()
=>
{
it
(
'
loads MarkdownExtension only for the markdown files
'
,
async
()
=>
{
await
initEditor
(
true
);
await
initEditor
(
true
);
expect
(
useMock
).
toHaveBeenCalledWith
(
expect
.
any
(
EditorMarkdownExtension
));
expect
(
useMock
).
toHaveBeenCalledTimes
(
2
);
expect
(
EditorMarkdownExtension
).
toHaveBeenCalledTimes
(
1
);
expect
(
useMock
.
mock
.
calls
[
1
]).
toEqual
([
markdownExtensions
]);
expect
(
EditorMarkdownExtension
).
toHaveBeenCalledWith
({
instance
:
mockInstance
,
previewMarkdownPath
:
PREVIEW_MARKDOWN_PATH
,
});
});
});
});
});
...
...
spec/frontend/editor/helpers.js
View file @
021bb329
/* eslint-disable max-classes-per-file */
// Helpers
export
const
spyOnApi
=
(
extension
,
spiesObj
=
{})
=>
{
const
origApi
=
extension
.
api
;
if
(
extension
?.
obj
)
{
jest
.
spyOn
(
extension
.
obj
,
'
provides
'
).
mockReturnValue
({
...
origApi
,
...
spiesObj
,
});
}
};
// Dummy Extensions
export
class
SEClassExtension
{
export
class
SEClassExtension
{
static
get
extensionName
()
{
return
'
SEClassExtension
'
;
}
// eslint-disable-next-line class-methods-use-this
// eslint-disable-next-line class-methods-use-this
provides
()
{
provides
()
{
return
{
return
{
...
@@ -10,6 +28,7 @@ export class SEClassExtension {
...
@@ -10,6 +28,7 @@ export class SEClassExtension {
export
function
SEFnExtension
()
{
export
function
SEFnExtension
()
{
return
{
return
{
extensionName
:
'
SEFnExtension
'
,
fnExtMethod
:
()
=>
'
fn own method
'
,
fnExtMethod
:
()
=>
'
fn own method
'
,
provides
:
()
=>
{
provides
:
()
=>
{
return
{
return
{
...
@@ -21,6 +40,7 @@ export function SEFnExtension() {
...
@@ -21,6 +40,7 @@ export function SEFnExtension() {
export
const
SEConstExt
=
()
=>
{
export
const
SEConstExt
=
()
=>
{
return
{
return
{
extensionName
:
'
SEConstExt
'
,
provides
:
()
=>
{
provides
:
()
=>
{
return
{
return
{
constExtMethod
:
()
=>
'
const own method
'
,
constExtMethod
:
()
=>
'
const own method
'
,
...
@@ -29,36 +49,39 @@ export const SEConstExt = () => {
...
@@ -29,36 +49,39 @@ export const SEConstExt = () => {
};
};
};
};
export
function
SEWithSetupExt
()
{
export
class
SEWithSetupExt
{
return
{
static
get
extensionName
()
{
onSetup
:
(
instance
,
setupOptions
=
{})
=>
{
return
'
SEWithSetupExt
'
;
if
(
setupOptions
&&
!
Array
.
isArray
(
setupOptions
))
{
}
Object
.
entries
(
setupOptions
).
forEach
(([
key
,
value
])
=>
{
// eslint-disable-next-line class-methods-use-this
Object
.
assign
(
instance
,
{
onSetup
(
instance
,
setupOptions
=
{})
{
[
key
]:
value
,
if
(
setupOptions
&&
!
Array
.
isArray
(
setupOptions
))
{
});
Object
.
entries
(
setupOptions
).
forEach
(([
key
,
value
])
=>
{
Object
.
assign
(
instance
,
{
[
key
]:
value
,
});
});
}
}
);
}
,
}
provides
:
()
=>
{
}
return
{
provides
()
{
returnInstanceAndProps
:
(
instance
,
stringProp
,
objProp
=
{})
=>
{
return
{
return
[
stringProp
,
objProp
,
instance
];
returnInstanceAndProps
:
(
instance
,
stringProp
,
objProp
=
{})
=>
{
},
return
[
stringProp
,
objProp
,
instance
];
returnInstance
:
(
instance
)
=>
{
},
return
instance
;
returnInstance
:
(
instance
)
=>
{
},
return
instance
;
giveMeContext
:
()
=>
{
},
return
this
;
giveMeContext
:
()
=>
{
},
return
this
;
}
;
}
,
}
,
}
;
}
;
}
}
}
export
const
conflictingExtensions
=
{
export
const
conflictingExtensions
=
{
WithInstanceExt
:
()
=>
{
WithInstanceExt
:
()
=>
{
return
{
return
{
extensionName
:
'
WithInstanceExt
'
,
provides
:
()
=>
{
provides
:
()
=>
{
return
{
return
{
use
:
()
=>
'
A conflict with instance
'
,
use
:
()
=>
'
A conflict with instance
'
,
...
@@ -69,6 +92,7 @@ export const conflictingExtensions = {
...
@@ -69,6 +92,7 @@ export const conflictingExtensions = {
},
},
WithAnotherExt
:
()
=>
{
WithAnotherExt
:
()
=>
{
return
{
return
{
extensionName
:
'
WithAnotherExt
'
,
provides
:
()
=>
{
provides
:
()
=>
{
return
{
return
{
shared
:
()
=>
'
A conflict with extension
'
,
shared
:
()
=>
'
A conflict with extension
'
,
...
...
spec/frontend/editor/source_editor_ci_schema_ext_spec.js
View file @
021bb329
...
@@ -23,7 +23,7 @@ describe('~/editor/editor_ci_config_ext', () => {
...
@@ -23,7 +23,7 @@ describe('~/editor/editor_ci_config_ext', () => {
blobPath
,
blobPath
,
blobContent
:
''
,
blobContent
:
''
,
});
});
instance
.
use
(
new
CiSchemaExtension
()
);
instance
.
use
(
{
definition
:
CiSchemaExtension
}
);
};
};
beforeAll
(()
=>
{
beforeAll
(()
=>
{
...
...
spec/frontend/editor/source_editor_extension_base_spec.js
View file @
021bb329
This diff is collapsed.
Click to expand it.
spec/frontend/editor/source_editor_extension_spec.js
View file @
021bb329
...
@@ -40,7 +40,7 @@ describe('Editor Extension', () => {
...
@@ -40,7 +40,7 @@ describe('Editor Extension', () => {
expect
(
extension
).
toEqual
(
expect
(
extension
).
toEqual
(
expect
.
objectContaining
({
expect
.
objectContaining
({
n
ame
:
expectedName
,
extensionN
ame
:
expectedName
,
setupOptions
,
setupOptions
,
}),
}),
);
);
...
...
spec/frontend/editor/source_editor_instance_spec.js
View file @
021bb329
...
@@ -32,11 +32,17 @@ describe('Source Editor Instance', () => {
...
@@ -32,11 +32,17 @@ describe('Source Editor Instance', () => {
];
];
const
fooFn
=
jest
.
fn
();
const
fooFn
=
jest
.
fn
();
const
fooProp
=
'
foo
'
;
class
DummyExt
{
class
DummyExt
{
// eslint-disable-next-line class-methods-use-this
get
extensionName
()
{
return
'
DummyExt
'
;
}
// eslint-disable-next-line class-methods-use-this
// eslint-disable-next-line class-methods-use-this
provides
()
{
provides
()
{
return
{
return
{
fooFn
,
fooFn
,
fooProp
,
};
};
}
}
}
}
...
@@ -64,7 +70,7 @@ describe('Source Editor Instance', () => {
...
@@ -64,7 +70,7 @@ describe('Source Editor Instance', () => {
});
});
describe
(
'
proxy
'
,
()
=>
{
describe
(
'
proxy
'
,
()
=>
{
it
(
'
returns
prop
from an extension if extension provides it
'
,
()
=>
{
it
(
'
returns
a method
from an extension if extension provides it
'
,
()
=>
{
seInstance
=
new
SourceEditorInstance
();
seInstance
=
new
SourceEditorInstance
();
seInstance
.
use
({
definition
:
DummyExt
});
seInstance
.
use
({
definition
:
DummyExt
});
...
@@ -73,6 +79,13 @@ describe('Source Editor Instance', () => {
...
@@ -73,6 +79,13 @@ describe('Source Editor Instance', () => {
expect
(
fooFn
).
toHaveBeenCalled
();
expect
(
fooFn
).
toHaveBeenCalled
();
});
});
it
(
'
returns a prop from an extension if extension provides it
'
,
()
=>
{
seInstance
=
new
SourceEditorInstance
();
seInstance
.
use
({
definition
:
DummyExt
});
expect
(
seInstance
.
fooProp
).
toBe
(
'
foo
'
);
});
it
.
each
`
it
.
each
`
stringPropToPass | objPropToPass | setupOptions
stringPropToPass | objPropToPass | setupOptions
${
undefined
}
|
${
undefined
}
|
${
undefined
}
${
undefined
}
|
${
undefined
}
|
${
undefined
}
...
@@ -118,20 +131,20 @@ describe('Source Editor Instance', () => {
...
@@ -118,20 +131,20 @@ describe('Source Editor Instance', () => {
it
(
"
correctly sets the context of the 'this' keyword for the extension's methods
"
,
()
=>
{
it
(
"
correctly sets the context of the 'this' keyword for the extension's methods
"
,
()
=>
{
seInstance
=
new
SourceEditorInstance
();
seInstance
=
new
SourceEditorInstance
();
seInstance
.
use
({
definition
:
SEWithSetupExt
});
const
extension
=
seInstance
.
use
({
definition
:
SEWithSetupExt
});
expect
(
seInstance
.
giveMeContext
()
.
constructor
).
toEqual
(
SEWithSetupExt
);
expect
(
seInstance
.
giveMeContext
()
).
toEqual
(
extension
.
obj
);
});
});
it
(
'
returns props from SE instance itself if no extension provides the prop
'
,
()
=>
{
it
(
'
returns props from SE instance itself if no extension provides the prop
'
,
()
=>
{
seInstance
=
new
SourceEditorInstance
({
seInstance
=
new
SourceEditorInstance
({
use
:
fooFn
,
use
:
fooFn
,
});
});
jest
.
spyOn
(
seInstanc
e
,
'
use
'
).
mockImplementation
(()
=>
{});
const
spy
=
jest
.
spyOn
(
seInstance
.
constructor
.
prototyp
e
,
'
use
'
).
mockImplementation
(()
=>
{});
expect
(
s
eInstance
.
use
).
not
.
toHaveBeenCalled
();
expect
(
s
py
).
not
.
toHaveBeenCalled
();
expect
(
fooFn
).
not
.
toHaveBeenCalled
();
expect
(
fooFn
).
not
.
toHaveBeenCalled
();
seInstance
.
use
();
seInstance
.
use
();
expect
(
s
eInstance
.
use
).
toHaveBeenCalled
();
expect
(
s
py
).
toHaveBeenCalled
();
expect
(
fooFn
).
not
.
toHaveBeenCalled
();
expect
(
fooFn
).
not
.
toHaveBeenCalled
();
});
});
...
...
spec/frontend/editor/source_editor_markdown_ext_spec.js
View file @
021bb329
...
@@ -9,7 +9,6 @@ describe('Markdown Extension for Source Editor', () => {
...
@@ -9,7 +9,6 @@ describe('Markdown Extension for Source Editor', () => {
let
instance
;
let
instance
;
let
editorEl
;
let
editorEl
;
let
mockAxios
;
let
mockAxios
;
const
previewMarkdownPath
=
'
/gitlab/fooGroup/barProj/preview_markdown
'
;
const
firstLine
=
'
This is a
'
;
const
firstLine
=
'
This is a
'
;
const
secondLine
=
'
multiline
'
;
const
secondLine
=
'
multiline
'
;
const
thirdLine
=
'
string with some **markup**
'
;
const
thirdLine
=
'
string with some **markup**
'
;
...
@@ -36,7 +35,7 @@ describe('Markdown Extension for Source Editor', () => {
...
@@ -36,7 +35,7 @@ describe('Markdown Extension for Source Editor', () => {
blobPath
:
markdownPath
,
blobPath
:
markdownPath
,
blobContent
:
text
,
blobContent
:
text
,
});
});
instance
.
use
(
new
EditorMarkdownExtension
({
instance
,
previewMarkdownPath
})
);
instance
.
use
(
{
definition
:
EditorMarkdownExtension
}
);
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -164,13 +163,11 @@ describe('Markdown Extension for Source Editor', () => {
...
@@ -164,13 +163,11 @@ describe('Markdown Extension for Source Editor', () => {
});
});
it
(
'
does not fail when only `toSelect` is supplied and fetches the text from selection
'
,
()
=>
{
it
(
'
does not fail when only `toSelect` is supplied and fetches the text from selection
'
,
()
=>
{
jest
.
spyOn
(
instance
,
'
getSelectedText
'
);
const
toSelect
=
'
string
'
;
const
toSelect
=
'
string
'
;
selectSecondAndThirdLines
();
selectSecondAndThirdLines
();
instance
.
selectWithinSelection
(
toSelect
);
instance
.
selectWithinSelection
(
toSelect
);
expect
(
instance
.
getSelectedText
).
toHaveBeenCalled
();
expect
(
selectionToString
()).
toBe
(
`[3,1 -> 3,
${
toSelect
.
length
+
1
}
]`
);
expect
(
selectionToString
()).
toBe
(
`[3,1 -> 3,
${
toSelect
.
length
+
1
}
]`
);
});
});
...
...
spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
View file @
021bb329
...
@@ -13,6 +13,7 @@ import SourceEditor from '~/editor/source_editor';
...
@@ -13,6 +13,7 @@ import SourceEditor from '~/editor/source_editor';
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
import
{
spyOnApi
}
from
'
./helpers
'
;
jest
.
mock
(
'
~/syntax_highlight
'
);
jest
.
mock
(
'
~/syntax_highlight
'
);
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/flash
'
);
...
@@ -23,6 +24,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -23,6 +24,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
let
editorEl
;
let
editorEl
;
let
panelSpy
;
let
panelSpy
;
let
mockAxios
;
let
mockAxios
;
let
extension
;
const
previewMarkdownPath
=
'
/gitlab/fooGroup/barProj/preview_markdown
'
;
const
previewMarkdownPath
=
'
/gitlab/fooGroup/barProj/preview_markdown
'
;
const
firstLine
=
'
This is a
'
;
const
firstLine
=
'
This is a
'
;
const
secondLine
=
'
multiline
'
;
const
secondLine
=
'
multiline
'
;
...
@@ -47,8 +49,11 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -47,8 +49,11 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
blobPath
:
markdownPath
,
blobPath
:
markdownPath
,
blobContent
:
text
,
blobContent
:
text
,
});
});
instance
.
use
(
new
EditorMarkdownPreviewExtension
({
instance
,
previewMarkdownPath
}));
extension
=
instance
.
use
({
panelSpy
=
jest
.
spyOn
(
EditorMarkdownPreviewExtension
,
'
togglePreviewPanel
'
);
definition
:
EditorMarkdownPreviewExtension
,
setupOptions
:
{
previewMarkdownPath
},
});
panelSpy
=
jest
.
spyOn
(
extension
.
obj
.
constructor
.
prototype
,
'
togglePreviewPanel
'
);
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -57,14 +62,14 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -57,14 +62,14 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
mockAxios
.
restore
();
mockAxios
.
restore
();
});
});
it
(
'
sets up the instance
'
,
()
=>
{
it
(
'
sets up the
preview on the
instance
'
,
()
=>
{
expect
(
instance
.
p
review
).
toEqual
({
expect
(
instance
.
markdownP
review
).
toEqual
({
el
:
undefined
,
el
:
undefined
,
action
:
expect
.
any
(
Object
),
action
:
expect
.
any
(
Object
),
shown
:
false
,
shown
:
false
,
modelChangeListener
:
undefined
,
modelChangeListener
:
undefined
,
path
:
previewMarkdownPath
,
});
});
expect
(
instance
.
previewMarkdownPath
).
toBe
(
previewMarkdownPath
);
});
});
describe
(
'
model language changes listener
'
,
()
=>
{
describe
(
'
model language changes listener
'
,
()
=>
{
...
@@ -72,14 +77,22 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -72,14 +77,22 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
let
actionSpy
;
let
actionSpy
;
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
cleanupSpy
=
jest
.
spyOn
(
instance
,
'
cleanup
'
);
cleanupSpy
=
jest
.
fn
();
actionSpy
=
jest
.
spyOn
(
instance
,
'
setupPreviewAction
'
);
actionSpy
=
jest
.
fn
();
spyOnApi
(
extension
,
{
cleanup
:
cleanupSpy
,
setupPreviewAction
:
actionSpy
,
});
await
togglePreview
();
await
togglePreview
();
});
});
afterEach
(()
=>
{
jest
.
clearAllMocks
();
});
it
(
'
cleans up when switching away from markdown
'
,
()
=>
{
it
(
'
cleans up when switching away from markdown
'
,
()
=>
{
expect
(
instance
.
cleanup
).
not
.
toHaveBeenCalled
();
expect
(
cleanupSpy
).
not
.
toHaveBeenCalled
();
expect
(
instance
.
setupPreviewAction
).
not
.
toHaveBeenCalled
();
expect
(
actionSpy
).
not
.
toHaveBeenCalled
();
instance
.
updateModelLanguage
(
plaintextPath
);
instance
.
updateModelLanguage
(
plaintextPath
);
...
@@ -110,8 +123,12 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -110,8 +123,12 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
let
actionSpy
;
let
actionSpy
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
cleanupSpy
=
jest
.
spyOn
(
instance
,
'
cleanup
'
);
cleanupSpy
=
jest
.
fn
();
actionSpy
=
jest
.
spyOn
(
instance
,
'
setupPreviewAction
'
);
actionSpy
=
jest
.
fn
();
spyOnApi
(
extension
,
{
cleanup
:
cleanupSpy
,
setupPreviewAction
:
actionSpy
,
});
instance
.
togglePreview
();
instance
.
togglePreview
();
});
});
...
@@ -153,14 +170,17 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -153,14 +170,17 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
disposes the modelChange listener and does not fetch preview on content changes
'
,
()
=>
{
it
(
'
disposes the modelChange listener and does not fetch preview on content changes
'
,
()
=>
{
expect
(
instance
.
preview
.
modelChangeListener
).
toBeDefined
();
expect
(
instance
.
markdownPreview
.
modelChangeListener
).
toBeDefined
();
jest
.
spyOn
(
instance
,
'
fetchPreview
'
);
const
fetchPreviewSpy
=
jest
.
fn
();
spyOnApi
(
extension
,
{
fetchPreview
:
fetchPreviewSpy
,
});
instance
.
cleanup
();
instance
.
cleanup
();
instance
.
setValue
(
'
Foo Bar
'
);
instance
.
setValue
(
'
Foo Bar
'
);
jest
.
advanceTimersByTime
(
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
);
jest
.
advanceTimersByTime
(
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY
);
expect
(
instance
.
fetchPreview
).
not
.
toHaveBeenCalled
();
expect
(
fetchPreviewSpy
).
not
.
toHaveBeenCalled
();
});
});
it
(
'
removes the contextual menu action
'
,
()
=>
{
it
(
'
removes the contextual menu action
'
,
()
=>
{
...
@@ -172,13 +192,13 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -172,13 +192,13 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
toggles the `shown` flag
'
,
()
=>
{
it
(
'
toggles the `shown` flag
'
,
()
=>
{
expect
(
instance
.
p
review
.
shown
).
toBe
(
true
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
true
);
instance
.
cleanup
();
instance
.
cleanup
();
expect
(
instance
.
p
review
.
shown
).
toBe
(
false
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
false
);
});
});
it
(
'
toggles the panel only if the preview is visible
'
,
()
=>
{
it
(
'
toggles the panel only if the preview is visible
'
,
()
=>
{
const
{
el
:
previewEl
}
=
instance
.
p
review
;
const
{
el
:
previewEl
}
=
instance
.
markdownP
review
;
const
parentEl
=
previewEl
.
parentElement
;
const
parentEl
=
previewEl
.
parentElement
;
expect
(
previewEl
).
toBeVisible
();
expect
(
previewEl
).
toBeVisible
();
...
@@ -200,7 +220,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -200,7 +220,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
it
(
'
toggles the layout only if the preview is visible
'
,
()
=>
{
it
(
'
toggles the layout only if the preview is visible
'
,
()
=>
{
const
{
width
}
=
instance
.
getLayoutInfo
();
const
{
width
}
=
instance
.
getLayoutInfo
();
expect
(
instance
.
p
review
.
shown
).
toBe
(
true
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
true
);
instance
.
cleanup
();
instance
.
cleanup
();
...
@@ -234,13 +254,13 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -234,13 +254,13 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
puts the fetched content into the preview DOM element
'
,
async
()
=>
{
it
(
'
puts the fetched content into the preview DOM element
'
,
async
()
=>
{
instance
.
p
review
.
el
=
editorEl
.
parentElement
;
instance
.
markdownP
review
.
el
=
editorEl
.
parentElement
;
await
fetchPreview
();
await
fetchPreview
();
expect
(
instance
.
p
review
.
el
.
innerHTML
).
toEqual
(
responseData
);
expect
(
instance
.
markdownP
review
.
el
.
innerHTML
).
toEqual
(
responseData
);
});
});
it
(
'
applies syntax highlighting to the preview content
'
,
async
()
=>
{
it
(
'
applies syntax highlighting to the preview content
'
,
async
()
=>
{
instance
.
p
review
.
el
=
editorEl
.
parentElement
;
instance
.
markdownP
review
.
el
=
editorEl
.
parentElement
;
await
fetchPreview
();
await
fetchPreview
();
expect
(
syntaxHighlight
).
toHaveBeenCalled
();
expect
(
syntaxHighlight
).
toHaveBeenCalled
();
});
});
...
@@ -266,14 +286,17 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -266,14 +286,17 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
toggles preview when the action is triggered
'
,
()
=>
{
it
(
'
toggles preview when the action is triggered
'
,
()
=>
{
jest
.
spyOn
(
instance
,
'
togglePreview
'
).
mockImplementation
();
const
togglePreviewSpy
=
jest
.
fn
();
spyOnApi
(
extension
,
{
togglePreview
:
togglePreviewSpy
,
});
expect
(
instance
.
togglePreview
).
not
.
toHaveBeenCalled
();
expect
(
togglePreviewSpy
).
not
.
toHaveBeenCalled
();
const
action
=
instance
.
getAction
(
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
);
const
action
=
instance
.
getAction
(
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID
);
action
.
run
();
action
.
run
();
expect
(
instance
.
togglePreview
).
toHaveBeenCalled
();
expect
(
togglePreviewSpy
).
toHaveBeenCalled
();
});
});
});
});
...
@@ -283,39 +306,39 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -283,39 +306,39 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
toggles preview flag on instance
'
,
()
=>
{
it
(
'
toggles preview flag on instance
'
,
()
=>
{
expect
(
instance
.
p
review
.
shown
).
toBe
(
false
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
false
);
instance
.
togglePreview
();
instance
.
togglePreview
();
expect
(
instance
.
p
review
.
shown
).
toBe
(
true
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
true
);
instance
.
togglePreview
();
instance
.
togglePreview
();
expect
(
instance
.
p
review
.
shown
).
toBe
(
false
);
expect
(
instance
.
markdownP
review
.
shown
).
toBe
(
false
);
});
});
describe
(
'
panel DOM element set up
'
,
()
=>
{
describe
(
'
panel DOM element set up
'
,
()
=>
{
it
(
'
sets up an element to contain the preview and stores it on instance
'
,
()
=>
{
it
(
'
sets up an element to contain the preview and stores it on instance
'
,
()
=>
{
expect
(
instance
.
p
review
.
el
).
toBeUndefined
();
expect
(
instance
.
markdownP
review
.
el
).
toBeUndefined
();
instance
.
togglePreview
();
instance
.
togglePreview
();
expect
(
instance
.
p
review
.
el
).
toBeDefined
();
expect
(
instance
.
markdownP
review
.
el
).
toBeDefined
();
expect
(
instance
.
preview
.
el
.
classList
.
contains
(
EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS
)).
toBe
(
expect
(
true
,
instance
.
markdownPreview
.
el
.
classList
.
contains
(
EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS
)
,
);
)
.
toBe
(
true
)
;
});
});
it
(
'
re-uses existing preview DOM element on repeated calls
'
,
()
=>
{
it
(
'
re-uses existing preview DOM element on repeated calls
'
,
()
=>
{
instance
.
togglePreview
();
instance
.
togglePreview
();
const
origPreviewEl
=
instance
.
p
review
.
el
;
const
origPreviewEl
=
instance
.
markdownP
review
.
el
;
instance
.
togglePreview
();
instance
.
togglePreview
();
expect
(
instance
.
p
review
.
el
).
toBe
(
origPreviewEl
);
expect
(
instance
.
markdownP
review
.
el
).
toBe
(
origPreviewEl
);
});
});
it
(
'
hides the preview DOM element by default
'
,
()
=>
{
it
(
'
hides the preview DOM element by default
'
,
()
=>
{
panelSpy
.
mockImplementation
();
panelSpy
.
mockImplementation
();
instance
.
togglePreview
();
instance
.
togglePreview
();
expect
(
instance
.
p
review
.
el
.
style
.
display
).
toBe
(
'
none
'
);
expect
(
instance
.
markdownP
review
.
el
.
style
.
display
).
toBe
(
'
none
'
);
});
});
});
});
...
@@ -350,9 +373,9 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -350,9 +373,9 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
it
(
'
toggles visibility of the preview DOM element
'
,
async
()
=>
{
it
(
'
toggles visibility of the preview DOM element
'
,
async
()
=>
{
await
togglePreview
();
await
togglePreview
();
expect
(
instance
.
p
review
.
el
.
style
.
display
).
toBe
(
'
block
'
);
expect
(
instance
.
markdownP
review
.
el
.
style
.
display
).
toBe
(
'
block
'
);
await
togglePreview
();
await
togglePreview
();
expect
(
instance
.
p
review
.
el
.
style
.
display
).
toBe
(
'
none
'
);
expect
(
instance
.
markdownP
review
.
el
.
style
.
display
).
toBe
(
'
none
'
);
});
});
describe
(
'
hidden preview DOM element
'
,
()
=>
{
describe
(
'
hidden preview DOM element
'
,
()
=>
{
...
@@ -367,9 +390,9 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -367,9 +390,9 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
});
});
it
(
'
stores disposable listener for model changes
'
,
async
()
=>
{
it
(
'
stores disposable listener for model changes
'
,
async
()
=>
{
expect
(
instance
.
p
review
.
modelChangeListener
).
toBeUndefined
();
expect
(
instance
.
markdownP
review
.
modelChangeListener
).
toBeUndefined
();
await
togglePreview
();
await
togglePreview
();
expect
(
instance
.
p
review
.
modelChangeListener
).
toBeDefined
();
expect
(
instance
.
markdownP
review
.
modelChangeListener
).
toBeDefined
();
});
});
});
});
...
@@ -386,7 +409,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
...
@@ -386,7 +409,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
it
(
'
disposes the model change event listener
'
,
()
=>
{
it
(
'
disposes the model change event listener
'
,
()
=>
{
const
disposeSpy
=
jest
.
fn
();
const
disposeSpy
=
jest
.
fn
();
instance
.
p
review
.
modelChangeListener
=
{
instance
.
markdownP
review
.
modelChangeListener
=
{
dispose
:
disposeSpy
,
dispose
:
disposeSpy
,
};
};
instance
.
togglePreview
();
instance
.
togglePreview
();
...
...
spec/frontend/editor/source_editor_spec.js
View file @
021bb329
This diff is collapsed.
Click to expand it.
spec/frontend/editor/source_editor_yaml_ext_spec.js
View file @
021bb329
...
@@ -2,6 +2,10 @@ import { Document } from 'yaml';
...
@@ -2,6 +2,10 @@ import { Document } from 'yaml';
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
{
YamlEditorExtension
}
from
'
~/editor/extensions/source_editor_yaml_ext
'
;
import
{
YamlEditorExtension
}
from
'
~/editor/extensions/source_editor_yaml_ext
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
{
spyOnApi
}
from
'
jest/editor/helpers
'
;
let
baseExtension
;
let
yamlExtension
;
const
getEditorInstance
=
(
editorInstanceOptions
=
{})
=>
{
const
getEditorInstance
=
(
editorInstanceOptions
=
{})
=>
{
setFixtures
(
'
<div id="editor"></div>
'
);
setFixtures
(
'
<div id="editor"></div>
'
);
...
@@ -16,7 +20,10 @@ const getEditorInstance = (editorInstanceOptions = {}) => {
...
@@ -16,7 +20,10 @@ const getEditorInstance = (editorInstanceOptions = {}) => {
const
getEditorInstanceWithExtension
=
(
extensionOptions
=
{},
editorInstanceOptions
=
{})
=>
{
const
getEditorInstanceWithExtension
=
(
extensionOptions
=
{},
editorInstanceOptions
=
{})
=>
{
setFixtures
(
'
<div id="editor"></div>
'
);
setFixtures
(
'
<div id="editor"></div>
'
);
const
instance
=
getEditorInstance
(
editorInstanceOptions
);
const
instance
=
getEditorInstance
(
editorInstanceOptions
);
instance
.
use
(
new
YamlEditorExtension
({
instance
,
...
extensionOptions
}));
[
baseExtension
,
yamlExtension
]
=
instance
.
use
([
{
definition
:
SourceEditorExtension
},
{
definition
:
YamlEditorExtension
,
setupOptions
:
extensionOptions
},
]);
// Remove the below once
// Remove the below once
// https://gitlab.com/gitlab-org/gitlab/-/issues/325992 is resolved
// https://gitlab.com/gitlab-org/gitlab/-/issues/325992 is resolved
...
@@ -29,19 +36,16 @@ const getEditorInstanceWithExtension = (extensionOptions = {}, editorInstanceOpt
...
@@ -29,19 +36,16 @@ const getEditorInstanceWithExtension = (extensionOptions = {}, editorInstanceOpt
describe
(
'
YamlCreatorExtension
'
,
()
=>
{
describe
(
'
YamlCreatorExtension
'
,
()
=>
{
describe
(
'
constructor
'
,
()
=>
{
describe
(
'
constructor
'
,
()
=>
{
it
(
'
saves constructor options
'
,
()
=>
{
it
(
'
saves setupOptions options on the extension, but does not expose those to instance
'
,
()
=>
{
const
highlightPath
=
'
foo
'
;
const
instance
=
getEditorInstanceWithExtension
({
const
instance
=
getEditorInstanceWithExtension
({
highlightPath
:
'
foo
'
,
highlightPath
,
enableComments
:
true
,
enableComments
:
true
,
});
});
expect
(
instance
).
toEqual
(
expect
(
yamlExtension
.
obj
.
highlightPath
).
toBe
(
highlightPath
);
expect
.
objectContaining
({
expect
(
yamlExtension
.
obj
.
enableComments
).
toBe
(
true
);
options
:
expect
.
objectContaining
({
expect
(
instance
.
highlightPath
).
toBeUndefined
();
highlightPath
:
'
foo
'
,
expect
(
instance
.
enableComments
).
toBeUndefined
();
enableComments
:
true
,
}),
}),
);
});
});
it
(
'
dumps values loaded with the model constructor options
'
,
()
=>
{
it
(
'
dumps values loaded with the model constructor options
'
,
()
=>
{
...
@@ -55,7 +59,7 @@ describe('YamlCreatorExtension', () => {
...
@@ -55,7 +59,7 @@ describe('YamlCreatorExtension', () => {
it
(
'
registers the onUpdate() function
'
,
()
=>
{
it
(
'
registers the onUpdate() function
'
,
()
=>
{
const
instance
=
getEditorInstance
();
const
instance
=
getEditorInstance
();
const
onDidChangeModelContent
=
jest
.
spyOn
(
instance
,
'
onDidChangeModelContent
'
);
const
onDidChangeModelContent
=
jest
.
spyOn
(
instance
,
'
onDidChangeModelContent
'
);
instance
.
use
(
new
YamlEditorExtension
({
instance
})
);
instance
.
use
(
{
definition
:
YamlEditorExtension
}
);
expect
(
onDidChangeModelContent
).
toHaveBeenCalledWith
(
expect
.
any
(
Function
));
expect
(
onDidChangeModelContent
).
toHaveBeenCalledWith
(
expect
.
any
(
Function
));
});
});
...
@@ -82,21 +86,21 @@ describe('YamlCreatorExtension', () => {
...
@@ -82,21 +86,21 @@ describe('YamlCreatorExtension', () => {
it
(
'
should call transformComments if enableComments is true
'
,
()
=>
{
it
(
'
should call transformComments if enableComments is true
'
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
({
enableComments
:
true
});
const
instance
=
getEditorInstanceWithExtension
({
enableComments
:
true
});
const
transformComments
=
jest
.
spyOn
(
YamlEditorExtension
,
'
transformComments
'
);
const
transformComments
=
jest
.
spyOn
(
YamlEditorExtension
,
'
transformComments
'
);
YamlEditorExtension
.
initFromModel
(
instance
,
model
);
instance
.
initFromModel
(
model
);
expect
(
transformComments
).
toHaveBeenCalled
();
expect
(
transformComments
).
toHaveBeenCalled
();
});
});
it
(
'
should not call transformComments if enableComments is false
'
,
()
=>
{
it
(
'
should not call transformComments if enableComments is false
'
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
({
enableComments
:
false
});
const
instance
=
getEditorInstanceWithExtension
({
enableComments
:
false
});
const
transformComments
=
jest
.
spyOn
(
YamlEditorExtension
,
'
transformComments
'
);
const
transformComments
=
jest
.
spyOn
(
YamlEditorExtension
,
'
transformComments
'
);
YamlEditorExtension
.
initFromModel
(
instance
,
model
);
instance
.
initFromModel
(
model
);
expect
(
transformComments
).
not
.
toHaveBeenCalled
();
expect
(
transformComments
).
not
.
toHaveBeenCalled
();
});
});
it
(
'
should call setValue with the stringified model
'
,
()
=>
{
it
(
'
should call setValue with the stringified model
'
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
();
const
instance
=
getEditorInstanceWithExtension
();
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
YamlEditorExtension
.
initFromModel
(
instance
,
model
);
instance
.
initFromModel
(
model
);
expect
(
setValue
).
toHaveBeenCalledWith
(
doc
.
toString
());
expect
(
setValue
).
toHaveBeenCalledWith
(
doc
.
toString
());
});
});
});
});
...
@@ -240,26 +244,35 @@ foo:
...
@@ -240,26 +244,35 @@ foo:
it
(
"
should call setValue with the stringified doc if the editor's value is empty
"
,
()
=>
{
it
(
"
should call setValue with the stringified doc if the editor's value is empty
"
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
();
const
instance
=
getEditorInstanceWithExtension
();
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
const
updateValue
=
jest
.
spyOn
(
instance
,
'
updateValue
'
);
const
updateValueSpy
=
jest
.
fn
();
spyOnApi
(
yamlExtension
,
{
updateValue
:
updateValueSpy
,
});
instance
.
setDoc
(
doc
);
instance
.
setDoc
(
doc
);
expect
(
setValue
).
toHaveBeenCalledWith
(
doc
.
toString
());
expect
(
setValue
).
toHaveBeenCalledWith
(
doc
.
toString
());
expect
(
updateValue
).
not
.
toHaveBeenCalled
();
expect
(
updateValue
Spy
).
not
.
toHaveBeenCalled
();
});
});
it
(
"
should call updateValue with the stringified doc if the editor's value is not empty
"
,
()
=>
{
it
(
"
should call updateValue with the stringified doc if the editor's value is not empty
"
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
({},
{
value
:
'
asjkdhkasjdh
'
});
const
instance
=
getEditorInstanceWithExtension
({},
{
value
:
'
asjkdhkasjdh
'
});
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
const
setValue
=
jest
.
spyOn
(
instance
,
'
setValue
'
);
const
updateValue
=
jest
.
spyOn
(
instance
,
'
updateValue
'
);
const
updateValueSpy
=
jest
.
fn
();
spyOnApi
(
yamlExtension
,
{
updateValue
:
updateValueSpy
,
});
instance
.
setDoc
(
doc
);
instance
.
setDoc
(
doc
);
expect
(
setValue
).
not
.
toHaveBeenCalled
();
expect
(
setValue
).
not
.
toHaveBeenCalled
();
expect
(
updateValue
).
toHaveBeenCalledWith
(
doc
.
toString
());
expect
(
updateValue
Spy
).
toHaveBeenCalledWith
(
instance
,
doc
.
toString
());
});
});
it
(
'
should trigger the onUpdate method
'
,
()
=>
{
it
(
'
should trigger the onUpdate method
'
,
()
=>
{
const
instance
=
getEditorInstanceWithExtension
();
const
instance
=
getEditorInstanceWithExtension
();
const
onUpdate
=
jest
.
spyOn
(
instance
,
'
onUpdate
'
);
const
onUpdateSpy
=
jest
.
fn
();
spyOnApi
(
yamlExtension
,
{
onUpdate
:
onUpdateSpy
,
});
instance
.
setDoc
(
doc
);
instance
.
setDoc
(
doc
);
expect
(
onUpdate
).
toHaveBeenCalled
();
expect
(
onUpdate
Spy
).
toHaveBeenCalled
();
});
});
});
});
...
@@ -320,9 +333,12 @@ foo:
...
@@ -320,9 +333,12 @@ foo:
it
(
'
calls highlight
'
,
()
=>
{
it
(
'
calls highlight
'
,
()
=>
{
const
highlightPath
=
'
foo
'
;
const
highlightPath
=
'
foo
'
;
const
instance
=
getEditorInstanceWithExtension
({
highlightPath
});
const
instance
=
getEditorInstanceWithExtension
({
highlightPath
});
instance
.
highlight
=
jest
.
fn
();
// Here we do not spy on the public API method of the extension, but rather
// the public method of the extension's instance.
// This is required based on how `onUpdate` works
const
highlightSpy
=
jest
.
spyOn
(
yamlExtension
.
obj
,
'
highlight
'
);
instance
.
onUpdate
();
instance
.
onUpdate
();
expect
(
instance
.
highlight
).
toHaveBeenCalledWith
(
highlightPath
);
expect
(
highlightSpy
).
toHaveBeenCalledWith
(
instance
,
highlightPath
);
});
});
});
});
...
@@ -350,8 +366,12 @@ foo:
...
@@ -350,8 +366,12 @@ foo:
beforeEach
(()
=>
{
beforeEach
(()
=>
{
instance
=
getEditorInstanceWithExtension
({
highlightPath
:
highlightPathOnSetup
},
{
value
});
instance
=
getEditorInstanceWithExtension
({
highlightPath
:
highlightPathOnSetup
},
{
value
});
highlightLinesSpy
=
jest
.
spyOn
(
SourceEditorExtension
,
'
highlightLines
'
);
highlightLinesSpy
=
jest
.
fn
();
removeHighlightsSpy
=
jest
.
spyOn
(
SourceEditorExtension
,
'
removeHighlights
'
);
removeHighlightsSpy
=
jest
.
fn
();
spyOnApi
(
baseExtension
,
{
highlightLines
:
highlightLinesSpy
,
removeHighlights
:
removeHighlightsSpy
,
});
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -361,7 +381,7 @@ foo:
...
@@ -361,7 +381,7 @@ foo:
it
(
'
saves the highlighted path in highlightPath
'
,
()
=>
{
it
(
'
saves the highlighted path in highlightPath
'
,
()
=>
{
const
path
=
'
foo.bar
'
;
const
path
=
'
foo.bar
'
;
instance
.
highlight
(
path
);
instance
.
highlight
(
path
);
expect
(
instance
.
options
.
highlightPath
).
toEqual
(
path
);
expect
(
yamlExtension
.
obj
.
highlightPath
).
toEqual
(
path
);
});
});
it
(
'
calls highlightLines with a number of lines
'
,
()
=>
{
it
(
'
calls highlightLines with a number of lines
'
,
()
=>
{
...
@@ -374,14 +394,14 @@ foo:
...
@@ -374,14 +394,14 @@ foo:
instance
.
highlight
(
null
);
instance
.
highlight
(
null
);
expect
(
removeHighlightsSpy
).
toHaveBeenCalledWith
(
instance
);
expect
(
removeHighlightsSpy
).
toHaveBeenCalledWith
(
instance
);
expect
(
highlightLinesSpy
).
not
.
toHaveBeenCalled
();
expect
(
highlightLinesSpy
).
not
.
toHaveBeenCalled
();
expect
(
instance
.
options
.
highlightPath
).
toBeNull
();
expect
(
yamlExtension
.
obj
.
highlightPath
).
toBeNull
();
});
});
it
(
'
throws an error if path is invalid and does not change the highlighted path
'
,
()
=>
{
it
(
'
throws an error if path is invalid and does not change the highlighted path
'
,
()
=>
{
expect
(()
=>
instance
.
highlight
(
'
invalidPath[0]
'
)).
toThrow
(
expect
(()
=>
instance
.
highlight
(
'
invalidPath[0]
'
)).
toThrow
(
'
The node invalidPath[0] could not be found inside the document.
'
,
'
The node invalidPath[0] could not be found inside the document.
'
,
);
);
expect
(
instance
.
options
.
highlightPath
).
toEqual
(
highlightPathOnSetup
);
expect
(
yamlExtension
.
obj
.
highlightPath
).
toEqual
(
highlightPathOnSetup
);
expect
(
highlightLinesSpy
).
not
.
toHaveBeenCalled
();
expect
(
highlightLinesSpy
).
not
.
toHaveBeenCalled
();
expect
(
removeHighlightsSpy
).
not
.
toHaveBeenCalled
();
expect
(
removeHighlightsSpy
).
not
.
toHaveBeenCalled
();
});
});
...
...
spec/frontend/ide/components/repo_editor_spec.js
View file @
021bb329
...
@@ -9,7 +9,7 @@ import waitUsingRealTimer from 'helpers/wait_using_real_timer';
...
@@ -9,7 +9,7 @@ import waitUsingRealTimer from 'helpers/wait_using_real_timer';
import
{
exampleConfigs
,
exampleFiles
}
from
'
jest/ide/lib/editorconfig/mock_data
'
;
import
{
exampleConfigs
,
exampleFiles
}
from
'
jest/ide/lib/editorconfig/mock_data
'
;
import
{
EDITOR_CODE_INSTANCE_FN
,
EDITOR_DIFF_INSTANCE_FN
}
from
'
~/editor/constants
'
;
import
{
EDITOR_CODE_INSTANCE_FN
,
EDITOR_DIFF_INSTANCE_FN
}
from
'
~/editor/constants
'
;
import
{
EditorMarkdownExtension
}
from
'
~/editor/extensions/source_editor_markdown_ext
'
;
import
{
EditorMarkdownExtension
}
from
'
~/editor/extensions/source_editor_markdown_ext
'
;
import
{
Editor
WebIdeExtension
}
from
'
~/editor/extensions/source_editor_webide
_ext
'
;
import
{
Editor
MarkdownPreviewExtension
}
from
'
~/editor/extensions/source_editor_markdown_livepreview
_ext
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
SourceEditor
from
'
~/editor/source_editor
'
;
import
RepoEditor
from
'
~/ide/components/repo_editor.vue
'
;
import
RepoEditor
from
'
~/ide/components/repo_editor.vue
'
;
import
{
import
{
...
@@ -23,6 +23,8 @@ import service from '~/ide/services';
...
@@ -23,6 +23,8 @@ import service from '~/ide/services';
import
{
createStoreOptions
}
from
'
~/ide/stores
'
;
import
{
createStoreOptions
}
from
'
~/ide/stores
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
ContentViewer
from
'
~/vue_shared/components/content_viewer/content_viewer.vue
'
;
import
ContentViewer
from
'
~/vue_shared/components/content_viewer/content_viewer.vue
'
;
import
SourceEditorInstance
from
'
~/editor/source_editor_instance
'
;
import
{
spyOnApi
}
from
'
jest/editor/helpers
'
;
import
{
file
}
from
'
../helpers
'
;
import
{
file
}
from
'
../helpers
'
;
const
PREVIEW_MARKDOWN_PATH
=
'
/foo/bar/preview_markdown
'
;
const
PREVIEW_MARKDOWN_PATH
=
'
/foo/bar/preview_markdown
'
;
...
@@ -101,6 +103,7 @@ describe('RepoEditor', () => {
...
@@ -101,6 +103,7 @@ describe('RepoEditor', () => {
let
createDiffInstanceSpy
;
let
createDiffInstanceSpy
;
let
createModelSpy
;
let
createModelSpy
;
let
applyExtensionSpy
;
let
applyExtensionSpy
;
let
extensionsStore
;
const
waitForEditorSetup
=
()
=>
const
waitForEditorSetup
=
()
=>
new
Promise
((
resolve
)
=>
{
new
Promise
((
resolve
)
=>
{
...
@@ -120,6 +123,7 @@ describe('RepoEditor', () => {
...
@@ -120,6 +123,7 @@ describe('RepoEditor', () => {
});
});
await
waitForPromises
();
await
waitForPromises
();
vm
=
wrapper
.
vm
;
vm
=
wrapper
.
vm
;
extensionsStore
=
wrapper
.
vm
.
globalEditor
.
extensionsStore
;
jest
.
spyOn
(
vm
,
'
getFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
vm
,
'
getFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
vm
,
'
getRawFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
vm
,
'
getRawFileData
'
).
mockResolvedValue
();
};
};
...
@@ -127,28 +131,12 @@ describe('RepoEditor', () => {
...
@@ -127,28 +131,12 @@ describe('RepoEditor', () => {
const
findEditor
=
()
=>
wrapper
.
find
(
'
[data-testid="editor-container"]
'
);
const
findEditor
=
()
=>
wrapper
.
find
(
'
[data-testid="editor-container"]
'
);
const
findTabs
=
()
=>
wrapper
.
findAll
(
'
.ide-mode-tabs .nav-links li
'
);
const
findTabs
=
()
=>
wrapper
.
findAll
(
'
.ide-mode-tabs .nav-links li
'
);
const
findPreviewTab
=
()
=>
wrapper
.
find
(
'
[data-testid="preview-tab"]
'
);
const
findPreviewTab
=
()
=>
wrapper
.
find
(
'
[data-testid="preview-tab"]
'
);
const
expectEditorMarkdownExtension
=
(
shouldHaveExtension
)
=>
{
if
(
shouldHaveExtension
)
{
expect
(
applyExtensionSpy
).
toHaveBeenCalledWith
(
wrapper
.
vm
.
editor
,
expect
.
any
(
EditorMarkdownExtension
),
);
// TODO: spying on extensions causes Jest to blow up, so we have to assert on
// the public property the extension adds, as opposed to the args passed to the ctor
expect
(
wrapper
.
vm
.
editor
.
previewMarkdownPath
).
toBe
(
PREVIEW_MARKDOWN_PATH
);
}
else
{
expect
(
applyExtensionSpy
).
not
.
toHaveBeenCalledWith
(
wrapper
.
vm
.
editor
,
expect
.
any
(
EditorMarkdownExtension
),
);
}
};
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createInstanceSpy
=
jest
.
spyOn
(
SourceEditor
.
prototype
,
EDITOR_CODE_INSTANCE_FN
);
createInstanceSpy
=
jest
.
spyOn
(
SourceEditor
.
prototype
,
EDITOR_CODE_INSTANCE_FN
);
createDiffInstanceSpy
=
jest
.
spyOn
(
SourceEditor
.
prototype
,
EDITOR_DIFF_INSTANCE_FN
);
createDiffInstanceSpy
=
jest
.
spyOn
(
SourceEditor
.
prototype
,
EDITOR_DIFF_INSTANCE_FN
);
createModelSpy
=
jest
.
spyOn
(
monacoEditor
,
'
createModel
'
);
createModelSpy
=
jest
.
spyOn
(
monacoEditor
,
'
createModel
'
);
applyExtensionSpy
=
jest
.
spyOn
(
SourceEditor
,
'
instanceApplyExtension
'
);
applyExtensionSpy
=
jest
.
spyOn
(
SourceEditor
Instance
.
prototype
,
'
use
'
);
jest
.
spyOn
(
service
,
'
getFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
service
,
'
getFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
service
,
'
getRawFileData
'
).
mockResolvedValue
();
jest
.
spyOn
(
service
,
'
getRawFileData
'
).
mockResolvedValue
();
});
});
...
@@ -275,14 +263,13 @@ describe('RepoEditor', () => {
...
@@ -275,14 +263,13 @@ describe('RepoEditor', () => {
);
);
it
(
'
installs the WebIDE extension
'
,
async
()
=>
{
it
(
'
installs the WebIDE extension
'
,
async
()
=>
{
const
extensionSpy
=
jest
.
spyOn
(
SourceEditor
,
'
instanceApplyExtension
'
);
await
createComponent
();
await
createComponent
();
expect
(
e
xtensionSpy
).
toHaveBeenCalled
();
expect
(
applyE
xtensionSpy
).
toHaveBeenCalled
();
Reflect
.
ownKeys
(
EditorWebIdeExtension
.
prototype
)
const
ideExtensionApi
=
extensionsStore
.
get
(
'
EditorWebIde
'
).
api
;
.
filter
((
fn
)
=>
fn
!==
'
constructor
'
)
Reflect
.
ownKeys
(
ideExtensionApi
).
forEach
((
fn
)
=>
{
.
forEach
((
fn
)
=>
{
expect
(
vm
.
editor
[
fn
]).
toBeDefined
();
expect
(
vm
.
editor
[
fn
]).
toBe
(
EditorWebIdeExtension
.
prototype
[
fn
]
);
expect
(
vm
.
editor
.
methods
[
fn
]).
toBe
(
'
EditorWebIde
'
);
});
});
});
});
it
.
each
`
it
.
each
`
...
@@ -301,7 +288,20 @@ describe('RepoEditor', () => {
...
@@ -301,7 +288,20 @@ describe('RepoEditor', () => {
async
({
activeFile
,
viewer
,
shouldHaveMarkdownExtension
}
=
{})
=>
{
async
({
activeFile
,
viewer
,
shouldHaveMarkdownExtension
}
=
{})
=>
{
await
createComponent
({
state
:
{
viewer
},
activeFile
});
await
createComponent
({
state
:
{
viewer
},
activeFile
});
expectEditorMarkdownExtension
(
shouldHaveMarkdownExtension
);
if
(
shouldHaveMarkdownExtension
)
{
expect
(
applyExtensionSpy
).
toHaveBeenCalledWith
({
definition
:
EditorMarkdownPreviewExtension
,
setupOptions
:
{
previewMarkdownPath
:
PREVIEW_MARKDOWN_PATH
},
});
// TODO: spying on extensions causes Jest to blow up, so we have to assert on
// the public property the extension adds, as opposed to the args passed to the ctor
expect
(
wrapper
.
vm
.
editor
.
markdownPreview
.
path
).
toBe
(
PREVIEW_MARKDOWN_PATH
);
}
else
{
expect
(
applyExtensionSpy
).
not
.
toHaveBeenCalledWith
(
wrapper
.
vm
.
editor
,
expect
.
any
(
EditorMarkdownExtension
),
);
}
},
},
);
);
});
});
...
@@ -329,18 +329,6 @@ describe('RepoEditor', () => {
...
@@ -329,18 +329,6 @@ describe('RepoEditor', () => {
expect
(
vm
.
model
).
toBe
(
existingModel
);
expect
(
vm
.
model
).
toBe
(
existingModel
);
});
});
it
(
'
adds callback methods
'
,
()
=>
{
jest
.
spyOn
(
vm
.
editor
,
'
onPositionChange
'
);
jest
.
spyOn
(
vm
.
model
,
'
onChange
'
);
jest
.
spyOn
(
vm
.
model
,
'
updateOptions
'
);
vm
.
setupEditor
();
expect
(
vm
.
editor
.
onPositionChange
).
toHaveBeenCalledTimes
(
1
);
expect
(
vm
.
model
.
onChange
).
toHaveBeenCalledTimes
(
1
);
expect
(
vm
.
model
.
updateOptions
).
toHaveBeenCalledWith
(
vm
.
rules
);
});
it
(
'
updates state with the value of the model
'
,
()
=>
{
it
(
'
updates state with the value of the model
'
,
()
=>
{
const
newContent
=
'
As Gregor Samsa
\n
awoke one morning
\n
'
;
const
newContent
=
'
As Gregor Samsa
\n
awoke one morning
\n
'
;
vm
.
model
.
setValue
(
newContent
);
vm
.
model
.
setValue
(
newContent
);
...
@@ -366,53 +354,48 @@ describe('RepoEditor', () => {
...
@@ -366,53 +354,48 @@ describe('RepoEditor', () => {
describe
(
'
editor updateDimensions
'
,
()
=>
{
describe
(
'
editor updateDimensions
'
,
()
=>
{
let
updateDimensionsSpy
;
let
updateDimensionsSpy
;
let
updateDiffViewSpy
;
beforeEach
(
async
()
=>
{
beforeEach
(
async
()
=>
{
await
createComponent
();
await
createComponent
();
updateDimensionsSpy
=
jest
.
spyOn
(
vm
.
editor
,
'
updateDimensions
'
);
const
ext
=
extensionsStore
.
get
(
'
EditorWebIde
'
);
updateDiffViewSpy
=
jest
.
spyOn
(
vm
.
editor
,
'
updateDiffView
'
).
mockImplementation
();
updateDimensionsSpy
=
jest
.
fn
();
spyOnApi
(
ext
,
{
updateDimensions
:
updateDimensionsSpy
,
});
});
});
it
(
'
calls updateDimensions only when panelResizing is false
'
,
async
()
=>
{
it
(
'
calls updateDimensions only when panelResizing is false
'
,
async
()
=>
{
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDiffViewSpy
).
not
.
toHaveBeenCalled
();
expect
(
vm
.
$store
.
state
.
panelResizing
).
toBe
(
false
);
// default value
expect
(
vm
.
$store
.
state
.
panelResizing
).
toBe
(
false
);
// default value
vm
.
$store
.
state
.
panelResizing
=
true
;
vm
.
$store
.
state
.
panelResizing
=
true
;
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDiffViewSpy
).
not
.
toHaveBeenCalled
();
vm
.
$store
.
state
.
panelResizing
=
false
;
vm
.
$store
.
state
.
panelResizing
=
false
;
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDiffViewSpy
).
toHaveBeenCalledTimes
(
1
);
vm
.
$store
.
state
.
panelResizing
=
true
;
vm
.
$store
.
state
.
panelResizing
=
true
;
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDiffViewSpy
).
toHaveBeenCalledTimes
(
1
);
});
});
it
(
'
calls updateDimensions when rightPane is toggled
'
,
async
()
=>
{
it
(
'
calls updateDimensions when rightPane is toggled
'
,
async
()
=>
{
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDimensionsSpy
).
not
.
toHaveBeenCalled
();
expect
(
updateDiffViewSpy
).
not
.
toHaveBeenCalled
();
expect
(
vm
.
$store
.
state
.
rightPane
.
isOpen
).
toBe
(
false
);
// default value
expect
(
vm
.
$store
.
state
.
rightPane
.
isOpen
).
toBe
(
false
);
// default value
vm
.
$store
.
state
.
rightPane
.
isOpen
=
true
;
vm
.
$store
.
state
.
rightPane
.
isOpen
=
true
;
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
updateDiffViewSpy
).
toHaveBeenCalledTimes
(
1
);
vm
.
$store
.
state
.
rightPane
.
isOpen
=
false
;
vm
.
$store
.
state
.
rightPane
.
isOpen
=
false
;
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
2
);
expect
(
updateDimensionsSpy
).
toHaveBeenCalledTimes
(
2
);
expect
(
updateDiffViewSpy
).
toHaveBeenCalledTimes
(
2
);
});
});
});
});
...
@@ -447,7 +430,11 @@ describe('RepoEditor', () => {
...
@@ -447,7 +430,11 @@ describe('RepoEditor', () => {
activeFile
:
dummyFile
.
markdown
,
activeFile
:
dummyFile
.
markdown
,
});
});
updateDimensionsSpy
=
jest
.
spyOn
(
vm
.
editor
,
'
updateDimensions
'
);
const
ext
=
extensionsStore
.
get
(
'
EditorWebIde
'
);
updateDimensionsSpy
=
jest
.
fn
();
spyOnApi
(
ext
,
{
updateDimensions
:
updateDimensionsSpy
,
});
changeViewMode
(
FILE_VIEW_MODE_PREVIEW
);
changeViewMode
(
FILE_VIEW_MODE_PREVIEW
);
await
vm
.
$nextTick
();
await
vm
.
$nextTick
();
...
...
spec/frontend/pipeline_editor/components/editor/text_editor_spec.js
View file @
021bb329
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
EDITOR_READY_EVENT
}
from
'
~/editor/constants
'
;
import
{
EDITOR_READY_EVENT
}
from
'
~/editor/constants
'
;
import
{
SourceEditorExtension
}
from
'
~/editor/extensions/source_editor_extension_base
'
;
import
TextEditor
from
'
~/pipeline_editor/components/editor/text_editor.vue
'
;
import
TextEditor
from
'
~/pipeline_editor/components/editor/text_editor.vue
'
;
import
{
import
{
mockCiConfigPath
,
mockCiConfigPath
,
...
@@ -59,10 +58,6 @@ describe('Pipeline Editor | Text editor component', () => {
...
@@ -59,10 +58,6 @@ describe('Pipeline Editor | Text editor component', () => {
const
findEditor
=
()
=>
wrapper
.
findComponent
(
MockSourceEditor
);
const
findEditor
=
()
=>
wrapper
.
findComponent
(
MockSourceEditor
);
beforeEach
(()
=>
{
SourceEditorExtension
.
deferRerender
=
jest
.
fn
();
});
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment