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
abb4b08e
Commit
abb4b08e
authored
Feb 06, 2020
by
Phil Hughes
Committed by
Nick Thomas
Feb 06, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Code navigation frontend components
https://gitlab.com/gitlab-org/gitlab/issues/196514
parent
6dde4feb
Changes
29
Hide whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
826 additions
and
2 deletions
+826
-2
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+9
-0
app/assets/javascripts/code_navigation/components/app.vue
app/assets/javascripts/code_navigation/components/app.vue
+43
-0
app/assets/javascripts/code_navigation/components/popover.vue
...assets/javascripts/code_navigation/components/popover.vue
+76
-0
app/assets/javascripts/code_navigation/index.js
app/assets/javascripts/code_navigation/index.js
+20
-0
app/assets/javascripts/code_navigation/store/actions.js
app/assets/javascripts/code_navigation/store/actions.js
+62
-0
app/assets/javascripts/code_navigation/store/index.js
app/assets/javascripts/code_navigation/store/index.js
+10
-0
app/assets/javascripts/code_navigation/store/mutation_types.js
...ssets/javascripts/code_navigation/store/mutation_types.js
+5
-0
app/assets/javascripts/code_navigation/store/mutations.js
app/assets/javascripts/code_navigation/store/mutations.js
+23
-0
app/assets/javascripts/code_navigation/store/state.js
app/assets/javascripts/code_navigation/store/state.js
+9
-0
app/assets/javascripts/code_navigation/utils/index.js
app/assets/javascripts/code_navigation/utils/index.js
+20
-0
app/assets/javascripts/pages/projects/blob/show/index.js
app/assets/javascripts/pages/projects/blob/show/index.js
+5
-0
app/assets/stylesheets/framework/files.scss
app/assets/stylesheets/framework/files.scss
+12
-0
app/controllers/projects/blob_controller.rb
app/controllers/projects/blob_controller.rb
+4
-0
app/helpers/projects_helper.rb
app/helpers/projects_helper.rb
+4
-0
app/views/projects/blob/_blob.html.haml
app/views/projects/blob/_blob.html.haml
+2
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/features/merge_request/maintainer_edits_fork_spec.rb
spec/features/merge_request/maintainer_edits_fork_spec.rb
+1
-1
spec/features/projects/blobs/blob_show_spec.rb
spec/features/projects/blobs/blob_show_spec.rb
+4
-0
spec/features/projects/blobs/edit_spec.rb
spec/features/projects/blobs/edit_spec.rb
+2
-0
spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
...ojects/blobs/user_creates_new_blob_in_new_project_spec.rb
+1
-0
spec/features/projects/files/user_creates_files_spec.rb
spec/features/projects/files/user_creates_files_spec.rb
+1
-1
spec/features/projects/files/user_deletes_files_spec.rb
spec/features/projects/files/user_deletes_files_spec.rb
+2
-0
spec/features/projects/files/user_replaces_files_spec.rb
spec/features/projects/files/user_replaces_files_spec.rb
+2
-0
spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
..._navigation/components/__snapshots__/popover_spec.js.snap
+39
-0
spec/frontend/code_navigation/components/app_spec.js
spec/frontend/code_navigation/components/app_spec.js
+64
-0
spec/frontend/code_navigation/components/popover_spec.js
spec/frontend/code_navigation/components/popover_spec.js
+58
-0
spec/frontend/code_navigation/store/actions_spec.js
spec/frontend/code_navigation/store/actions_spec.js
+221
-0
spec/frontend/code_navigation/store/mutations_spec.js
spec/frontend/code_navigation/store/mutations_spec.js
+63
-0
spec/frontend/code_navigation/utils/index_spec.js
spec/frontend/code_navigation/utils/index_spec.js
+58
-0
No files found.
app/assets/javascripts/api.js
View file @
abb4b08e
...
...
@@ -45,6 +45,7 @@ const Api = {
mergeRequestsPipeline
:
'
/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines
'
,
adminStatisticsPath
:
'
/api/:version/application/statistics
'
,
pipelineSinglePath
:
'
/api/:version/projects/:id/pipelines/:pipeline_id
'
,
lsifPath
:
'
/api/:version/projects/:id/commits/:commit_id/lsif/info
'
,
group
(
groupId
,
callback
)
{
const
url
=
Api
.
buildUrl
(
Api
.
groupPath
).
replace
(
'
:id
'
,
groupId
);
...
...
@@ -457,6 +458,14 @@ const Api = {
return
axios
.
get
(
url
);
},
lsifData
(
projectPath
,
commitId
,
path
)
{
const
url
=
Api
.
buildUrl
(
this
.
lsifPath
)
.
replace
(
'
:id
'
,
encodeURIComponent
(
projectPath
))
.
replace
(
'
:commit_id
'
,
commitId
);
return
axios
.
get
(
url
,
{
params
:
{
path
}
});
},
buildUrl
(
url
)
{
return
joinPaths
(
gon
.
relative_url_root
||
''
,
url
.
replace
(
'
:version
'
,
gon
.
api_version
));
},
...
...
app/assets/javascripts/code_navigation/components/app.vue
0 → 100644
View file @
abb4b08e
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
Popover
from
'
./popover.vue
'
;
export
default
{
components
:
{
Popover
,
},
computed
:
{
...
mapState
([
'
currentDefinition
'
,
'
currentDefinitionPosition
'
]),
},
mounted
()
{
this
.
blobViewer
=
document
.
querySelector
(
'
.blob-viewer
'
);
this
.
addGlobalEventListeners
();
this
.
fetchData
();
},
beforeDestroy
()
{
this
.
removeGlobalEventListeners
();
},
methods
:
{
...
mapActions
([
'
fetchData
'
,
'
showDefinition
'
]),
addGlobalEventListeners
()
{
if
(
this
.
blobViewer
)
{
this
.
blobViewer
.
addEventListener
(
'
click
'
,
this
.
showDefinition
);
}
},
removeGlobalEventListeners
()
{
if
(
this
.
blobViewer
)
{
this
.
blobViewer
.
removeEventListener
(
'
click
'
,
this
.
showDefinition
);
}
},
},
};
</
script
>
<
template
>
<popover
v-if=
"currentDefinition"
:position=
"currentDefinitionPosition"
:data=
"currentDefinition"
/>
</
template
>
app/assets/javascripts/code_navigation/components/popover.vue
0 → 100644
View file @
abb4b08e
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlButton
,
},
props
:
{
position
:
{
type
:
Object
,
required
:
true
,
},
data
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
offsetLeft
:
0
,
};
},
computed
:
{
positionStyles
()
{
return
{
left
:
`
${
this
.
position
.
x
-
this
.
offsetLeft
}
px`
,
top
:
`
${
this
.
position
.
y
+
this
.
position
.
height
}
px`
,
};
},
},
watch
:
{
position
:
{
handler
()
{
this
.
$nextTick
(()
=>
this
.
updateOffsetLeft
());
},
deep
:
true
,
immediate
:
true
,
},
},
methods
:
{
updateOffsetLeft
()
{
this
.
offsetLeft
=
Math
.
max
(
0
,
this
.
$el
.
offsetLeft
+
this
.
$el
.
offsetWidth
-
window
.
innerWidth
+
20
,
);
},
},
colorScheme
:
gon
?.
user_color_scheme
,
};
</
script
>
<
template
>
<div
:style=
"positionStyles"
class=
"popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show"
>
<div
:style=
"
{ left: `${offsetLeft}px` }" class="arrow">
</div>
<div
v-for=
"(hover, index) in data.hover"
:key=
"index"
class=
"border-bottom"
>
<pre
v-if=
"hover.language"
ref=
"code-output"
:class=
"$options.colorScheme"
class=
"border-0 bg-transparent m-0 code highlight"
v-html=
"hover.value"
></pre>
<p
v-else
ref=
"doc-output"
class=
"p-3 m-0"
>
{{
hover
.
value
}}
</p>
</div>
<div
v-if=
"data.definition_url"
class=
"popover-body"
>
<gl-button
:href=
"data.definition_url"
target=
"_blank"
class=
"w-100"
variant=
"default"
>
{{
__
(
'
Go to definition
'
)
}}
</gl-button>
</div>
</div>
</
template
>
app/assets/javascripts/code_navigation/index.js
0 → 100644
View file @
abb4b08e
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
store
from
'
./store
'
;
import
App
from
'
./components/app.vue
'
;
Vue
.
use
(
Vuex
);
export
default
()
=>
{
const
el
=
document
.
getElementById
(
'
js-code-navigation
'
);
store
.
dispatch
(
'
setInitialData
'
,
el
.
dataset
);
return
new
Vue
({
el
,
store
,
render
(
h
)
{
return
h
(
App
);
},
});
};
app/assets/javascripts/code_navigation/store/actions.js
0 → 100644
View file @
abb4b08e
import
api
from
'
~/api
'
;
import
{
__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
getCurrentHoverElement
,
setCurrentHoverElement
,
addInteractionClass
}
from
'
../utils
'
;
export
default
{
setInitialData
({
commit
},
data
)
{
commit
(
types
.
SET_INITIAL_DATA
,
data
);
},
requestDataError
({
commit
})
{
commit
(
types
.
REQUEST_DATA_ERROR
);
createFlash
(
__
(
'
An error occurred loading code navigation
'
));
},
fetchData
({
commit
,
dispatch
,
state
})
{
commit
(
types
.
REQUEST_DATA
);
api
.
lsifData
(
state
.
projectPath
,
state
.
commitId
,
state
.
path
)
.
then
(({
data
})
=>
{
const
normalizedData
=
data
.
reduce
((
acc
,
d
)
=>
{
if
(
d
.
hover
)
{
acc
[
`
${
d
.
start_line
}
:
${
d
.
start_char
}
`
]
=
d
;
addInteractionClass
(
d
);
}
return
acc
;
},
{});
commit
(
types
.
REQUEST_DATA_SUCCESS
,
normalizedData
);
})
.
catch
(()
=>
dispatch
(
'
requestDataError
'
));
},
showDefinition
({
commit
,
state
},
{
target
:
el
})
{
let
definition
;
let
position
;
if
(
!
state
.
data
)
return
;
const
isCurrentElementPopoverOpen
=
el
.
classList
.
contains
(
'
hll
'
);
if
(
getCurrentHoverElement
())
{
getCurrentHoverElement
().
classList
.
remove
(
'
hll
'
);
}
if
(
el
.
classList
.
contains
(
'
js-code-navigation
'
)
&&
!
isCurrentElementPopoverOpen
)
{
const
{
lineIndex
,
charIndex
}
=
el
.
dataset
;
position
=
{
x
:
el
.
offsetLeft
,
y
:
el
.
offsetTop
,
height
:
el
.
offsetHeight
,
};
definition
=
state
.
data
[
`
${
lineIndex
}
:
${
charIndex
}
`
];
el
.
classList
.
add
(
'
hll
'
);
setCurrentHoverElement
(
el
);
}
commit
(
types
.
SET_CURRENT_DEFINITION
,
{
definition
,
position
});
},
};
app/assets/javascripts/code_navigation/store/index.js
0 → 100644
View file @
abb4b08e
import
Vuex
from
'
vuex
'
;
import
createState
from
'
./state
'
;
import
actions
from
'
./actions
'
;
import
mutations
from
'
./mutations
'
;
export
default
new
Vuex
.
Store
({
actions
,
mutations
,
state
:
createState
(),
});
app/assets/javascripts/code_navigation/store/mutation_types.js
0 → 100644
View file @
abb4b08e
export
const
SET_INITIAL_DATA
=
'
SET_INITIAL_DATA
'
;
export
const
REQUEST_DATA
=
'
REQUEST_DATA
'
;
export
const
REQUEST_DATA_SUCCESS
=
'
REQUEST_DATA_SUCCESS
'
;
export
const
REQUEST_DATA_ERROR
=
'
REQUEST_DATA_ERROR
'
;
export
const
SET_CURRENT_DEFINITION
=
'
SET_CURRENT_DEFINITION
'
;
app/assets/javascripts/code_navigation/store/mutations.js
0 → 100644
View file @
abb4b08e
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
SET_INITIAL_DATA
](
state
,
{
projectPath
,
commitId
,
blobPath
})
{
state
.
projectPath
=
projectPath
;
state
.
commitId
=
commitId
;
state
.
blobPath
=
blobPath
;
},
[
types
.
REQUEST_DATA
](
state
)
{
state
.
loading
=
true
;
},
[
types
.
REQUEST_DATA_SUCCESS
](
state
,
data
)
{
state
.
loading
=
false
;
state
.
data
=
data
;
},
[
types
.
REQUEST_DATA_ERROR
](
state
)
{
state
.
loading
=
false
;
},
[
types
.
SET_CURRENT_DEFINITION
](
state
,
{
definition
,
position
})
{
state
.
currentDefinition
=
definition
;
state
.
currentDefinitionPosition
=
position
;
},
};
app/assets/javascripts/code_navigation/store/state.js
0 → 100644
View file @
abb4b08e
export
default
()
=>
({
projectPath
:
null
,
commitId
:
null
,
blobPath
:
null
,
loading
:
false
,
data
:
null
,
currentDefinition
:
null
,
currentDefinitionPosition
:
null
,
});
app/assets/javascripts/code_navigation/utils/index.js
0 → 100644
View file @
abb4b08e
export
const
cachedData
=
new
Map
();
export
const
getCurrentHoverElement
=
()
=>
cachedData
.
get
(
'
current
'
);
export
const
setCurrentHoverElement
=
el
=>
cachedData
.
set
(
'
current
'
,
el
);
export
const
addInteractionClass
=
d
=>
{
let
charCount
=
0
;
const
line
=
document
.
getElementById
(
`LC
${
d
.
start_line
+
1
}
`
);
const
el
=
[...
line
.
childNodes
].
find
(({
textContent
})
=>
{
if
(
charCount
===
d
.
start_char
)
return
true
;
charCount
+=
textContent
.
length
;
return
false
;
});
if
(
el
)
{
el
.
setAttribute
(
'
data-char-index
'
,
d
.
start_char
);
el
.
setAttribute
(
'
data-line-index
'
,
d
.
start_line
);
el
.
classList
.
add
(
'
cursor-pointer
'
,
'
code-navigation
'
,
'
js-code-navigation
'
);
}
};
app/assets/javascripts/pages/projects/blob/show/index.js
View file @
abb4b08e
...
...
@@ -30,4 +30,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
GpgBadges
.
fetch
();
if
(
gon
.
features
?.
codeNavigation
)
{
// eslint-disable-next-line promise/catch-or-return
import
(
'
~/code_navigation
'
).
then
(
m
=>
m
.
default
());
}
});
app/assets/stylesheets/framework/files.scss
View file @
abb4b08e
...
...
@@ -499,3 +499,15 @@ span.idiff {
background-color
:
transparent
;
border
:
transparent
;
}
.code-navigation
{
border-bottom
:
1px
$gray-darkest
dashed
;
&
:hover
{
border-bottom-color
:
$almost-black
;
}
}
.code-navigation-popover
{
max-width
:
450px
;
}
app/controllers/projects/blob_controller.rb
View file @
abb4b08e
...
...
@@ -29,6 +29,10 @@ class Projects::BlobController < Projects::ApplicationController
before_action
:validate_diff_params
,
only: :diff
before_action
:set_last_commit_sha
,
only:
[
:edit
,
:update
]
before_action
only: :show
do
push_frontend_feature_flag
(
:code_navigation
,
@project
)
end
def
new
commit
unless
@repository
.
empty?
end
...
...
app/helpers/projects_helper.rb
View file @
abb4b08e
...
...
@@ -706,6 +706,10 @@ module ProjectsHelper
Feature
.
enabled?
(
:vue_file_list
,
@project
)
end
def
native_code_navigation_enabled?
(
project
)
Feature
.
enabled?
(
:code_navigation
,
project
)
end
def
show_visibility_confirm_modal?
(
project
)
project
.
unlink_forks_upon_visibility_decrease_enabled?
&&
project
.
visibility_level
>
Gitlab
::
VisibilityLevel
::
PRIVATE
&&
project
.
forks_count
>
0
end
...
...
app/views/projects/blob/_blob.html.haml
View file @
abb4b08e
...
...
@@ -9,6 +9,8 @@
=
render
"projects/blob/auxiliary_viewer"
,
blob:
blob
#blob-content-holder
.blob-content-holder
-
if
native_code_navigation_enabled?
(
@project
)
#js-code-navigation
{
data:
{
commit_id:
blob
.
commit_id
,
path:
blob
.
path
,
project_path:
@project
.
full_path
}
}
%article
.file-holder
=
render
'projects/blob/header'
,
blob:
blob
=
render
'projects/blob/content'
,
blob:
blob
locale/gitlab.pot
View file @
abb4b08e
...
...
@@ -1670,6 +1670,9 @@ msgstr ""
msgid "An error occurred fetching the dropdown data."
msgstr ""
msgid "An error occurred loading code navigation"
msgstr ""
msgid "An error occurred previewing the blob"
msgstr ""
...
...
@@ -9297,6 +9300,9 @@ msgstr ""
msgid "Go to commits"
msgstr ""
msgid "Go to definition"
msgstr ""
msgid "Go to environments"
msgstr ""
...
...
spec/features/merge_request/maintainer_edits_fork_spec.rb
View file @
abb4b08e
...
...
@@ -20,7 +20,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
end
before
do
stub_feature_flags
(
web_ide_default:
false
,
single_mr_diff_view:
false
)
stub_feature_flags
(
web_ide_default:
false
,
single_mr_diff_view:
false
,
code_navigation:
false
)
target_project
.
add_maintainer
(
user
)
sign_in
(
user
)
...
...
spec/features/projects/blobs/blob_show_spec.rb
View file @
abb4b08e
...
...
@@ -13,6 +13,10 @@ describe 'File blob', :js do
wait_for_requests
end
before
do
stub_feature_flags
(
code_navigation:
false
)
end
context
'Ruby file'
do
before
do
visit_blob
(
'files/ruby/popen.rb'
)
...
...
spec/features/projects/blobs/edit_spec.rb
View file @
abb4b08e
...
...
@@ -69,6 +69,8 @@ describe 'Editing file blob', :js do
context
'from blob file path'
do
before
do
stub_feature_flags
(
code_navigation:
false
)
visit
project_blob_path
(
project
,
tree_join
(
branch
,
file_path
))
end
...
...
spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
View file @
abb4b08e
...
...
@@ -8,6 +8,7 @@ describe 'User creates blob in new project', :js do
shared_examples
'creating a file'
do
before
do
stub_feature_flags
(
code_navigation:
false
)
sign_in
(
user
)
visit
project_path
(
project
)
end
...
...
spec/features/projects/files/user_creates_files_spec.rb
View file @
abb4b08e
...
...
@@ -14,7 +14,7 @@ describe 'Projects > Files > User creates files', :js do
let
(
:user
)
{
create
(
:user
)
}
before
do
stub_feature_flags
(
web_ide_default:
false
)
stub_feature_flags
(
web_ide_default:
false
,
code_navigation:
false
)
project
.
add_maintainer
(
user
)
sign_in
(
user
)
...
...
spec/features/projects/files/user_deletes_files_spec.rb
View file @
abb4b08e
...
...
@@ -14,6 +14,8 @@ describe 'Projects > Files > User deletes files', :js do
let
(
:user
)
{
create
(
:user
)
}
before
do
stub_feature_flags
(
code_navigation:
false
)
sign_in
(
user
)
end
...
...
spec/features/projects/files/user_replaces_files_spec.rb
View file @
abb4b08e
...
...
@@ -16,6 +16,8 @@ describe 'Projects > Files > User replaces files', :js do
let
(
:user
)
{
create
(
:user
)
}
before
do
stub_feature_flags
(
code_navigation:
false
)
sign_in
(
user
)
end
...
...
spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
0 → 100644
View file @
abb4b08e
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Code navigation popover component renders popover 1`] = `
<div
class="popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show"
style="left: 0px; top: 0px;"
>
<div
class="arrow"
style="left: 0px;"
/>
<div
class="border-bottom"
>
<pre
class="border-0 bg-transparent m-0 code highlight"
>
console.log
</pre>
</div>
<div
class="popover-body"
>
<gl-button-stub
class="w-100"
href="http://test.com"
size="md"
target="_blank"
variant="default"
>
Go to definition
</gl-button-stub>
</div>
</div>
`;
spec/frontend/code_navigation/components/app_spec.js
0 → 100644
View file @
abb4b08e
import
Vuex
from
'
vuex
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
createState
from
'
~/code_navigation/store/state
'
;
import
App
from
'
~/code_navigation/components/app.vue
'
;
import
Popover
from
'
~/code_navigation/components/popover.vue
'
;
const
localVue
=
createLocalVue
();
const
fetchData
=
jest
.
fn
();
const
showDefinition
=
jest
.
fn
();
let
wrapper
;
localVue
.
use
(
Vuex
);
function
factory
(
initialState
=
{})
{
const
store
=
new
Vuex
.
Store
({
state
:
{
...
createState
(),
...
initialState
,
},
actions
:
{
fetchData
,
showDefinition
,
},
});
wrapper
=
shallowMount
(
App
,
{
store
,
localVue
});
}
describe
(
'
Code navigation app component
'
,
()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
fetches data on mount
'
,
()
=>
{
factory
();
expect
(
fetchData
).
toHaveBeenCalled
();
});
it
(
'
hides popover when no definition set
'
,
()
=>
{
factory
();
expect
(
wrapper
.
find
(
Popover
).
exists
()).
toBe
(
false
);
});
it
(
'
renders popover when definition set
'
,
()
=>
{
factory
({
currentDefinition
:
{
hover
:
'
console
'
},
currentDefinitionPosition
:
{
x
:
0
},
});
expect
(
wrapper
.
find
(
Popover
).
exists
()).
toBe
(
true
);
});
it
(
'
calls showDefinition when clicking blob viewer
'
,
()
=>
{
setFixtures
(
'
<div class="blob-viewer"></div>
'
);
factory
();
document
.
querySelector
(
'
.blob-viewer
'
).
click
();
expect
(
showDefinition
).
toHaveBeenCalled
();
});
});
spec/frontend/code_navigation/components/popover_spec.js
0 → 100644
View file @
abb4b08e
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Popover
from
'
~/code_navigation/components/popover.vue
'
;
const
MOCK_CODE_DATA
=
Object
.
freeze
({
hover
:
[
{
language
:
'
javascript
'
,
value
:
'
console.log
'
,
},
],
definition_url
:
'
http://test.com
'
,
});
const
MOCK_DOCS_DATA
=
Object
.
freeze
({
hover
:
[
{
language
:
null
,
value
:
'
console.log
'
,
},
],
definition_url
:
'
http://test.com
'
,
});
let
wrapper
;
function
factory
(
position
,
data
)
{
wrapper
=
shallowMount
(
Popover
,
{
propsData
:
{
position
,
data
}
});
}
describe
(
'
Code navigation popover component
'
,
()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders popover
'
,
()
=>
{
factory
({
x
:
0
,
y
:
0
,
height
:
0
},
MOCK_CODE_DATA
);
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
describe
(
'
code output
'
,
()
=>
{
it
(
'
renders code output
'
,
()
=>
{
factory
({
x
:
0
,
y
:
0
,
height
:
0
},
MOCK_CODE_DATA
);
expect
(
wrapper
.
find
({
ref
:
'
code-output
'
}).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
({
ref
:
'
doc-output
'
}).
exists
()).
toBe
(
false
);
});
});
describe
(
'
documentation output
'
,
()
=>
{
it
(
'
renders code output
'
,
()
=>
{
factory
({
x
:
0
,
y
:
0
,
height
:
0
},
MOCK_DOCS_DATA
);
expect
(
wrapper
.
find
({
ref
:
'
code-output
'
}).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
({
ref
:
'
doc-output
'
}).
exists
()).
toBe
(
true
);
});
});
});
spec/frontend/code_navigation/store/actions_spec.js
0 → 100644
View file @
abb4b08e
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
testAction
from
'
helpers/vuex_action_helper
'
;
import
actions
from
'
~/code_navigation/store/actions
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
setCurrentHoverElement
,
addInteractionClass
}
from
'
~/code_navigation/utils
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/code_navigation/utils
'
);
describe
(
'
Code navigation actions
'
,
()
=>
{
describe
(
'
setInitialData
'
,
()
=>
{
it
(
'
commits SET_INITIAL_DATA
'
,
done
=>
{
testAction
(
actions
.
setInitialData
,
{
projectPath
:
'
test
'
},
{},
[{
type
:
'
SET_INITIAL_DATA
'
,
payload
:
{
projectPath
:
'
test
'
}
}],
[],
done
,
);
});
});
describe
(
'
requestDataError
'
,
()
=>
{
it
(
'
commits REQUEST_DATA_ERROR
'
,
()
=>
testAction
(
actions
.
requestDataError
,
null
,
{},
[{
type
:
'
REQUEST_DATA_ERROR
'
}],
[]));
it
(
'
creates a flash message
'
,
()
=>
testAction
(
actions
.
requestDataError
,
null
,
{},
[{
type
:
'
REQUEST_DATA_ERROR
'
}],
[]).
then
(
()
=>
{
expect
(
createFlash
).
toHaveBeenCalled
();
},
));
});
describe
(
'
fetchData
'
,
()
=>
{
let
mock
;
const
state
=
{
projectPath
:
'
gitlab-org/gitlab
'
,
commitId
:
'
123
'
,
blobPath
:
'
index
'
,
};
const
apiUrl
=
'
/api/1/projects/gitlab-org%2Fgitlab/commits/123/lsif/info
'
;
beforeEach
(()
=>
{
window
.
gon
=
{
api_version
:
'
1
'
};
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
success
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
apiUrl
).
replyOnce
(
200
,
[
{
start_line
:
0
,
start_char
:
0
,
hover
:
{
value
:
'
123
'
},
},
{
start_line
:
1
,
start_char
:
0
,
hover
:
null
,
},
]);
});
it
(
'
commits REQUEST_DATA_SUCCESS with normalized data
'
,
done
=>
{
testAction
(
actions
.
fetchData
,
null
,
state
,
[
{
type
:
'
REQUEST_DATA
'
},
{
type
:
'
REQUEST_DATA_SUCCESS
'
,
payload
:
{
'
0:0
'
:
{
start_line
:
0
,
start_char
:
0
,
hover
:
{
value
:
'
123
'
}
}
},
},
],
[],
done
,
);
});
it
(
'
calls addInteractionClass with data
'
,
done
=>
{
testAction
(
actions
.
fetchData
,
null
,
state
,
[
{
type
:
'
REQUEST_DATA
'
},
{
type
:
'
REQUEST_DATA_SUCCESS
'
,
payload
:
{
'
0:0
'
:
{
start_line
:
0
,
start_char
:
0
,
hover
:
{
value
:
'
123
'
}
}
},
},
],
[],
)
.
then
(()
=>
{
expect
(
addInteractionClass
).
toHaveBeenCalledWith
({
start_line
:
0
,
start_char
:
0
,
hover
:
{
value
:
'
123
'
},
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
describe
(
'
error
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
apiUrl
).
replyOnce
(
500
);
});
it
(
'
dispatches requestDataError
'
,
done
=>
{
testAction
(
actions
.
fetchData
,
null
,
state
,
[{
type
:
'
REQUEST_DATA
'
}],
[{
type
:
'
requestDataError
'
}],
done
,
);
});
});
});
describe
(
'
showDefinition
'
,
()
=>
{
let
target
;
beforeEach
(()
=>
{
target
=
document
.
createElement
(
'
div
'
);
});
it
(
'
returns early when no data exists
'
,
done
=>
{
testAction
(
actions
.
showDefinition
,
{
target
},
{},
[],
[],
done
);
});
it
(
'
commits SET_CURRENT_DEFINITION when target is not code navitation element
'
,
done
=>
{
testAction
(
actions
.
showDefinition
,
{
target
},
{
data
:
{}
},
[
{
type
:
'
SET_CURRENT_DEFINITION
'
,
payload
:
{
definition
:
undefined
,
position
:
undefined
},
},
],
[],
done
,
);
});
it
(
'
commits SET_CURRENT_DEFINITION with LSIF data
'
,
done
=>
{
target
.
classList
.
add
(
'
js-code-navigation
'
);
target
.
setAttribute
(
'
data-line-index
'
,
'
0
'
);
target
.
setAttribute
(
'
data-char-index
'
,
'
0
'
);
testAction
(
actions
.
showDefinition
,
{
target
},
{
data
:
{
'
0:0
'
:
{
hover
:
'
test
'
}
}
},
[
{
type
:
'
SET_CURRENT_DEFINITION
'
,
payload
:
{
definition
:
{
hover
:
'
test
'
},
position
:
{
height
:
0
,
x
:
0
,
y
:
0
}
},
},
],
[],
done
,
);
});
it
(
'
adds hll class to target element
'
,
()
=>
{
target
.
classList
.
add
(
'
js-code-navigation
'
);
target
.
setAttribute
(
'
data-line-index
'
,
'
0
'
);
target
.
setAttribute
(
'
data-char-index
'
,
'
0
'
);
return
testAction
(
actions
.
showDefinition
,
{
target
},
{
data
:
{
'
0:0
'
:
{
hover
:
'
test
'
}
}
},
[
{
type
:
'
SET_CURRENT_DEFINITION
'
,
payload
:
{
definition
:
{
hover
:
'
test
'
},
position
:
{
height
:
0
,
x
:
0
,
y
:
0
}
},
},
],
[],
).
then
(()
=>
{
expect
(
target
.
classList
).
toContain
(
'
hll
'
);
});
});
it
(
'
caches current target element
'
,
()
=>
{
target
.
classList
.
add
(
'
js-code-navigation
'
);
target
.
setAttribute
(
'
data-line-index
'
,
'
0
'
);
target
.
setAttribute
(
'
data-char-index
'
,
'
0
'
);
return
testAction
(
actions
.
showDefinition
,
{
target
},
{
data
:
{
'
0:0
'
:
{
hover
:
'
test
'
}
}
},
[
{
type
:
'
SET_CURRENT_DEFINITION
'
,
payload
:
{
definition
:
{
hover
:
'
test
'
},
position
:
{
height
:
0
,
x
:
0
,
y
:
0
}
},
},
],
[],
).
then
(()
=>
{
expect
(
setCurrentHoverElement
).
toHaveBeenCalledWith
(
target
);
});
});
});
});
spec/frontend/code_navigation/store/mutations_spec.js
0 → 100644
View file @
abb4b08e
import
mutations
from
'
~/code_navigation/store/mutations
'
;
import
createState
from
'
~/code_navigation/store/state
'
;
let
state
;
describe
(
'
Code navigation mutations
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
createState
();
});
describe
(
'
SET_INITIAL_DATA
'
,
()
=>
{
it
(
'
sets initial data
'
,
()
=>
{
mutations
.
SET_INITIAL_DATA
(
state
,
{
projectPath
:
'
test
'
,
commitId
:
'
123
'
,
blobPath
:
'
index.js
'
,
});
expect
(
state
.
projectPath
).
toBe
(
'
test
'
);
expect
(
state
.
commitId
).
toBe
(
'
123
'
);
expect
(
state
.
blobPath
).
toBe
(
'
index.js
'
);
});
});
describe
(
'
REQUEST_DATA
'
,
()
=>
{
it
(
'
sets loading true
'
,
()
=>
{
mutations
.
REQUEST_DATA
(
state
);
expect
(
state
.
loading
).
toBe
(
true
);
});
});
describe
(
'
REQUEST_DATA_SUCCESS
'
,
()
=>
{
it
(
'
sets loading false
'
,
()
=>
{
mutations
.
REQUEST_DATA_SUCCESS
(
state
,
[
'
test
'
]);
expect
(
state
.
loading
).
toBe
(
false
);
});
it
(
'
sets data
'
,
()
=>
{
mutations
.
REQUEST_DATA_SUCCESS
(
state
,
[
'
test
'
]);
expect
(
state
.
data
).
toEqual
([
'
test
'
]);
});
});
describe
(
'
REQUEST_DATA_ERROR
'
,
()
=>
{
it
(
'
sets loading false
'
,
()
=>
{
mutations
.
REQUEST_DATA_ERROR
(
state
);
expect
(
state
.
loading
).
toBe
(
false
);
});
});
describe
(
'
SET_CURRENT_DEFINITION
'
,
()
=>
{
it
(
'
sets current definition and position
'
,
()
=>
{
mutations
.
SET_CURRENT_DEFINITION
(
state
,
{
definition
:
'
test
'
,
position
:
{
x
:
0
}
});
expect
(
state
.
currentDefinition
).
toBe
(
'
test
'
);
expect
(
state
.
currentDefinitionPosition
).
toEqual
({
x
:
0
});
});
});
});
spec/frontend/code_navigation/utils/index_spec.js
0 → 100644
View file @
abb4b08e
import
{
cachedData
,
getCurrentHoverElement
,
setCurrentHoverElement
,
addInteractionClass
,
}
from
'
~/code_navigation/utils
'
;
afterEach
(()
=>
{
if
(
cachedData
.
has
(
'
current
'
))
{
cachedData
.
delete
(
'
current
'
);
}
});
describe
(
'
getCurrentHoverElement
'
,
()
=>
{
it
.
each
`
value
${
'
test
'
}
${
undefined
}
`
(
'
it returns cached current key
'
,
({
value
})
=>
{
if
(
value
)
{
cachedData
.
set
(
'
current
'
,
value
);
}
expect
(
getCurrentHoverElement
()).
toEqual
(
value
);
});
});
describe
(
'
setCurrentHoverElement
'
,
()
=>
{
it
(
'
sets cached current key
'
,
()
=>
{
setCurrentHoverElement
(
'
test
'
);
expect
(
getCurrentHoverElement
()).
toEqual
(
'
test
'
);
});
});
describe
(
'
addInteractionClass
'
,
()
=>
{
beforeEach
(()
=>
{
setFixtures
(
'
<div id="LC1"><span>console</span><span>.</span><span>log</span></div><div id="LC2"><span>function</span></div>
'
,
);
});
it
.
each
`
line | char | index
${
0
}
|
${
0
}
|
${
0
}
${
0
}
|
${
8
}
|
${
2
}
${
1
}
|
${
0
}
|
${
0
}
`
(
'
it sets code navigation attributes for line $line and character $char
'
,
({
line
,
char
,
index
})
=>
{
addInteractionClass
({
start_line
:
line
,
start_char
:
char
});
expect
(
document
.
querySelectorAll
(
`#LC
${
line
+
1
}
span`
)[
index
].
classList
).
toContain
(
'
js-code-navigation
'
,
);
},
);
});
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