Skip to content

Commit 63eecfa

Browse files
authored
ACM-23378 Support light mode in ACM editors (#4947)
* ACM-23378 Support light mode in ACM editors Signed-off-by: John Swanke <jswanke@redhat.com> * fix tests Signed-off-by: John Swanke <jswanke@redhat.com> * fix SyncEditor tests Signed-off-by: John Swanke <jswanke@redhat.com> * fix test Signed-off-by: John Swanke <jswanke@redhat.com> * fix systemdefault Signed-off-by: John Swanke <jswanke@redhat.com> * add test diagniostices Signed-off-by: John Swanke <jswanke@redhat.com> * test diagnostics Signed-off-by: John Swanke <jswanke@redhat.com> * test diagnostics Signed-off-by: John Swanke <jswanke@redhat.com> * backout test changes Signed-off-by: John Swanke <jswanke@redhat.com> * enable/fix all tests Signed-off-by: John Swanke <jswanke@redhat.com> * fix cross-talk Signed-off-by: John Swanke <jswanke@redhat.com> * test metrics Signed-off-by: John Swanke <jswanke@redhat.com> * fix random test failures Signed-off-by: John Swanke <jswanke@redhat.com> * fix tests Signed-off-by: John Swanke <jswanke@redhat.com> * fix coverage Signed-off-by: John Swanke <jswanke@redhat.com> * fix coverage Signed-off-by: John Swanke <jswanke@redhat.com> --------- Signed-off-by: John Swanke <jswanke@redhat.com>
1 parent 7be2e53 commit 63eecfa

20 files changed

Lines changed: 615 additions & 140 deletions

File tree

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/* Copyright Contributors to the Open Cluster Management project */
2+
import React from 'react'
3+
import { fireEvent } from '@testing-library/react'
4+
5+
class Range {
6+
startLineNumber: number | undefined
7+
endLineNumber: number | undefined
8+
endColumn: number | undefined
9+
startColumn: number | undefined
10+
constructor(startLineNumber?: number, startColumn?: number, endLineNumber?: number, endColumn?: number) {
11+
this.endLineNumber = endLineNumber
12+
this.endColumn = endColumn
13+
this.startLineNumber = startLineNumber
14+
this.startColumn = startColumn
15+
}
16+
containsPosition(position) {
17+
return Range.containsPosition(this, position)
18+
}
19+
static containsPosition(range, position) {
20+
if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) {
21+
return false
22+
}
23+
if (position.lineNumber === range.startLineNumber && position.column < range.startColumn) {
24+
return false
25+
}
26+
if (position.lineNumber === range.endLineNumber && position.column > range.endColumn) {
27+
return false
28+
}
29+
return true
30+
}
31+
}
32+
class Selection {
33+
startLineNumber: number | undefined
34+
selectionStartLineNumber: number | undefined
35+
selectionStartColumn: number | undefined
36+
endLineNumber: number | undefined
37+
endColumn: number | undefined
38+
startColumn: number | undefined
39+
constructor(startLineNumber?: number, startColumn?: number, endLineNumber?: number, endColumn?: number) {
40+
this.endLineNumber = endLineNumber
41+
this.endColumn = endColumn
42+
this.startLineNumber = startLineNumber
43+
this.selectionStartLineNumber = startLineNumber
44+
this.startColumn = startColumn
45+
this.selectionStartColumn = startColumn
46+
}
47+
}
48+
49+
interface MockModel {
50+
_commandManager: {
51+
future: any[]
52+
past: any[]
53+
}
54+
forceTokenization: () => void
55+
getValue: () => string
56+
setValue: (value: string) => void
57+
getLineContent: (line: number) => string
58+
getAllDecorations: () => any[]
59+
getLineCount: () => void
60+
getFullModelRange: () => void
61+
getValueInRange: () => string
62+
canUndo: () => boolean
63+
canRedo: () => boolean
64+
onDidChangeContent: () => void
65+
findMatches: (find: string) => { range: Range }[]
66+
}
67+
68+
interface MockEditor {
69+
layout: () => void
70+
focus: () => void
71+
trigger: () => void
72+
onKeyDown: () => void
73+
onMouseDown: () => void
74+
getVisibleRanges: () => any[]
75+
onDidBlurEditorWidget: () => void
76+
changeViewZones: () => void
77+
getDomNode: () => HTMLElement
78+
addCommand: () => void
79+
getSelection: () => void
80+
setSelection: () => void
81+
setSelections: () => void
82+
setTheme: (theme: any) => void
83+
saveViewState: () => void
84+
restoreViewState: () => any
85+
revealLineInCenter: () => void
86+
onDidChangeModelContent: (cb: any) => void
87+
deltaDecorations: () => void
88+
getModel: () => MockModel
89+
getValue: () => string
90+
executeEdits: (id: string, edits: [{ range: Range; text: string }]) => void
91+
}
92+
93+
interface MockMonaco {
94+
editor: { setModelLanguage: () => void; defineTheme: () => void; setTheme: () => void }
95+
languages: { registerHoverProvider: () => void }
96+
KeyMod: any
97+
KeyCode: any
98+
Range: Range
99+
Selection: Selection
100+
}
101+
102+
const MonacoEditor = (props: {
103+
value: string
104+
onChange(value: string, e: any): unknown
105+
onMount: (editor: MockEditor, monaco: MockMonaco) => void
106+
wrapperClassName: any
107+
}) => {
108+
const editorMockRef = React.useRef<any | null>(null)
109+
if (!editorMockRef.current) {
110+
editorMockRef.current = {}
111+
editorMockRef.current.lastTypeInx = -1
112+
editorMockRef.current.undoStack = [props.value]
113+
editorMockRef.current.redoStack = []
114+
editorMockRef.current.editorContent = props.value
115+
const model: MockModel = {
116+
_commandManager: {
117+
future: ['future'],
118+
past: ['past'],
119+
},
120+
forceTokenization: () => {},
121+
getLineCount: () => {
122+
const text = editorMockRef.current.editorContent
123+
return text.trim().split('\n').length
124+
},
125+
getFullModelRange: () => {},
126+
canUndo: () => true,
127+
canRedo: () => true,
128+
getValue: () => {
129+
return editorMockRef.current.editorContent
130+
},
131+
setValue: (value: string) => {
132+
editorMockRef.current.editorContent = value
133+
editorMockRef.current.undoStack = [value]
134+
},
135+
getLineContent: (line) => {
136+
return editorMockRef.current.editorContent.split('\n')[line]
137+
},
138+
getAllDecorations: () => [],
139+
getValueInRange: () => '',
140+
onDidChangeContent: () => {},
141+
findMatches: (find: string) => {
142+
return find === 'that' ? [{ range: new Range(0, 0, 0, 1) }, { range: new Range(0, 1, 0, 2) }] : []
143+
},
144+
}
145+
editorMockRef.current.mockEditor = {
146+
layout: () => {},
147+
focus: () => {},
148+
trigger: (_source, action) => {
149+
switch (action) {
150+
case 'undo':
151+
editorMockRef.current.undoRedo = true
152+
editorMockRef.current.redoStack.push(editorMockRef.current.undoStack.pop())
153+
fireEvent.change(editorMockRef.current.textArea, {
154+
target: {
155+
value: editorMockRef.current.undoStack[editorMockRef.current.undoStack.length - 1],
156+
},
157+
})
158+
break
159+
case 'redo':
160+
editorMockRef.current.undoRedo = true
161+
const value = editorMockRef.current.redoStack.pop()
162+
editorMockRef.current.undoStack.push(value)
163+
fireEvent.change(editorMockRef.current.textArea, {
164+
target: { value },
165+
})
166+
break
167+
}
168+
},
169+
onKeyDown: (handler) => {
170+
editorMockRef.current.onKeyDown = handler
171+
},
172+
onClick: () => {},
173+
onMouseDown: (handler) => {
174+
editorMockRef.current.onMouseDown = handler
175+
},
176+
onDidBlurEditorWidget: (handler) => {
177+
editorMockRef.current.onDidBlurEditorWidget = handler
178+
},
179+
getVisibleRanges: () => [],
180+
addCommand: () => {},
181+
changeViewZones: () => {},
182+
getDomNode: () => {
183+
return editorMockRef.current.textArea
184+
},
185+
getSelection: () => {
186+
const ta = editorMockRef.current.textArea
187+
const value = ta.value
188+
const startLines = value.slice(0, ta.selectionStart).split('\n')
189+
const startLineNumber = startLines.length - 1
190+
const startColumn = startLines[startLineNumber].length + 1
191+
const endLines = value.slice(0, ta.selectionEnd).split('\n')
192+
const endLineNumber = endLines.length - 1
193+
const endColumn = ta.selectionEnd - startLines.join('').length
194+
return new Selection(startLineNumber, startColumn, endLineNumber, endColumn)
195+
},
196+
setSelection: () => {},
197+
setSelections: () => {},
198+
saveViewState: () => null,
199+
setTheme: () => null,
200+
restoreViewState: () => {},
201+
revealLineInCenter: () => {},
202+
onDidChangeModelContent: (cb: any) => {
203+
editorMockRef.current.changeModelCallback = cb
204+
},
205+
deltaDecorations: (_oldDecorations: string[], newDecorations: any[]) => {
206+
editorMockRef.current.newDecorations = JSON.stringify(newDecorations)
207+
},
208+
getModel: () => model,
209+
getValue: () => {
210+
return editorMockRef.current.editorContent
211+
},
212+
executeEdits: (id, edits) => {
213+
const { text } = edits[0]
214+
const ta = editorMockRef.current.textArea
215+
const v = editorMockRef.current.textArea.value
216+
editorMockRef.current.textArea.value =
217+
v.substring(0, ta.selectionStart) + text + v.substring(ta.selectionEnd, v.length)
218+
},
219+
}
220+
editorMockRef.current.mockMonaco = {
221+
editor: { setModelLanguage: () => {}, defineTheme: () => {}, setTheme: () => {} },
222+
languages: { registerHoverProvider: () => {} },
223+
KeyMod: {},
224+
KeyCode: {},
225+
Range: Range,
226+
Selection: Selection,
227+
}
228+
props.onMount(editorMockRef.current.mockEditor, editorMockRef.current.mockMonaco)
229+
}
230+
return (
231+
<textarea
232+
aria-label="monaco"
233+
data-auto={props.wrapperClassName}
234+
data-decorators={editorMockRef.current.newDecorations}
235+
className="monaco-editor"
236+
ref={(ref) => {
237+
editorMockRef.current.textArea = ref
238+
}}
239+
onClick={(e) => {}}
240+
onMouseDown={(e) => {
241+
const editorEvent = {
242+
target: {
243+
position: {
244+
lineNumber: 12,
245+
column: 12,
246+
},
247+
},
248+
}
249+
editorMockRef.current.onMouseDown(editorEvent)
250+
}}
251+
onFocus={() => {
252+
editorMockRef.current.textArea.classList.add('focused')
253+
}}
254+
onBlur={() => {
255+
editorMockRef.current.textArea.classList.remove('focused')
256+
editorMockRef.current.onDidBlurEditorWidget()
257+
}}
258+
onChange={(e) => {
259+
if (editorMockRef.current.undoRedo === true) {
260+
editorMockRef.current.undoRedo = false
261+
} else {
262+
if (editorMockRef.current.textArea.selectionEnd !== editorMockRef.current.lastTypeInx + 1) {
263+
editorMockRef.current.undoStack.push(e.target.value)
264+
} else {
265+
editorMockRef.current.undoStack[editorMockRef.current.undoStack.length - 1] = e.target.value
266+
}
267+
editorMockRef.current.lastTypeInx = editorMockRef.current.textArea.selectionEnd
268+
}
269+
editorMockRef.current.editorContent = e.target.value
270+
props.onChange(editorMockRef.current.editorContent, e)
271+
//editorMockRef.current.changeModelCallback()
272+
}}
273+
value={editorMockRef.current.editorContent}
274+
></textarea>
275+
)
276+
}
277+
278+
export default MonacoEditor

frontend/__mocks__/react-monaco-editor.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ interface MockEditor {
8686
onDidChangeModelContent: (cb: any) => void
8787
deltaDecorations: () => void
8888
getModel: () => MockModel
89+
getValue: () => string
8990
executeEdits: (id: string, edits: [{ range: Range; text: string }]) => void
9091
}
9192

@@ -205,6 +206,9 @@ const MonacoEditor = (props: {
205206
editorMockRef.current.newDecorations = JSON.stringify(newDecorations)
206207
},
207208
getModel: () => model,
209+
getValue: () => {
210+
return editorMockRef.current.editorContent
211+
},
208212
executeEdits: (id, edits) => {
209213
const { text } = edits[0]
210214
const ta = editorMockRef.current.textArea

frontend/src/components/SyncEditor/SyncEditor.css

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
.sync-editor__container .pf-v5-c-code-editor__header {
2121
font-size: 14px;
2222
font-weight: 600;
23-
color: #ededed;
24-
background-color: #25282c;
23+
color: var(--pf-v5-c-label--Color);
24+
background-color: var(--pf-v5-c-page__main-section--BackgroundColor);
25+
border-bottom: 1px solid var(--pf-v5-global--BorderColor--100);
2526
}
2627

2728
.sync-editor__container .pf-v5-c-code-editor__controls {
@@ -34,22 +35,22 @@
3435
}
3536

3637
.sync-editor__container .pf-v5-c-code-editor__controls .pf-v5-c-button.pf-m-control {
37-
background-color: #25282c;
38+
background-color: var(--pf-v5-c-page__main-section--BackgroundColor);
3839
}
3940

4041
.sync-editor__container .sy-toolbar-buttons .pf-v5-c-button.pf-m-plain,
4142
.pf-v5-c-code-editor__controls .pf-v5-c-button.pf-m-control:not([disabled]) {
42-
color: #ededed;
43+
color: var(--pf-v5-c-label--Color);
4344
}
44-
45+
/*
4546
.sync-editor__container .pf-v5-c-button::after {
4647
border: none;
4748
border-left: 1px solid #55585c;
48-
}
49+
} */
4950

5051
.sync-editor__container .pf-v5-c-button:hover {
51-
color: #ededed;
52-
background-color: #25282c;
52+
color: var(--pf-v5-c-label--Color);
53+
background-color: var(--pf-v5-c-page__main-section--BackgroundColor);
5354
}
5455

5556
.sync-editor__container .pf-v5-c-code-editor__main {
@@ -67,7 +68,6 @@
6768
}
6869

6970
.filterDecoration {
70-
/* color: #b0b0b0 !important; */
7171
cursor: pointer;
7272
user-select: auto;
7373
}

frontend/src/components/SyncEditor/SyncEditor.test.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Decorators =
2424
}
2525
}[]
2626

27-
describe.skip('SyncEditor component', () => {
27+
describe('SyncEditor component', () => {
2828
afterAll(() => {
2929
jest.resetAllMocks()
3030
})
@@ -195,17 +195,16 @@ describe.skip('SyncEditor component', () => {
195195
i = input.value.indexOf(text)
196196
input.setSelectionRange(i, i + text.length)
197197
userEvent.type(input, 'newthing')
198-
await new Promise((resolve) => setTimeout(resolve, 500)) // wait for debounce
198+
await new Promise((resolve) => setTimeout(resolve, 2500)) // wait for debounce
199199

200200
// make sure first user edit is still there
201201
// make sure form change is still there
202202
// make sure last user edit is still good
203203
// make sure decorators show what's protected
204-
expect(get(onEditorChange.mock.calls, '1.0.resources.0.spec.disabled')).toBeFalsy()
205-
expect(get(onEditorChange.mock.calls, '1.0.resources.0.metadata.annotations.test')).toBe('me')
206-
expect(get(onEditorChange.mock.calls, '1.0.resources.1.spec.clusterSelector.matchExpressions.0.values.0')).toBe(
207-
'newthing'
208-
)
204+
const lastChange = onEditorChange.mock.calls[onEditorChange.mock.calls.length - 1]
205+
expect(get(lastChange, '0.resources.0.spec.disabled')).toBeFalsy()
206+
expect(get(lastChange, '0.resources.0.metadata.annotations.test')).toBe('me')
207+
expect(get(lastChange, '0.resources.1.spec.clusterSelector.matchExpressions.0.values.0')).toBe('newthing')
209208
const decorators = JSON.parse(input.dataset['decorators'] || '')
210209
expect(decorators).toEqual(protectedDecorators)
211210
})

0 commit comments

Comments
 (0)