Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 2300d65

Browse files
myan9k8s-ci-robot
authored andcommitted
fix(plugins/plugin-client-common): snapshot doesn't preserve cancelled or empty block
1 parent a3dc8cc commit 2300d65

File tree

7 files changed

+221
-75
lines changed

7 files changed

+221
-75
lines changed

packages/test/src/api/util.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import * as CLI from './cli'
2424
import * as Common from './common'
2525
import * as ReplExpect from './repl-expect'
2626
import * as SidecarExpect from './sidecar-expect'
27+
import { keys } from './keys'
2728

2829
export interface AppAndCount {
2930
app: Application
@@ -397,3 +398,24 @@ export async function clickSidecarButtonCustomized(ctx: Common.ISuite, res: AppA
397398
await _.click()
398399
})
399400
}
401+
402+
export function doCancel(this: Common.ISuite, cmd = '') {
403+
return this.app.client
404+
.$(Selectors.CURRENT_PROMPT_BLOCK)
405+
.then(async _ => {
406+
_.waitForExist()
407+
return _.getAttribute('data-input-count')
408+
})
409+
.then(count => parseInt(count, 10))
410+
.then(count =>
411+
this.app.client
412+
.keys(cmd)
413+
.then(() => this.app.client.keys(keys.ctrlC))
414+
.then(() => ({ app: this.app, count: count }))
415+
.then(ReplExpect.blank)
416+
.then(() => this.app.client.$(Selectors.PROMPT_N(count))) // make sure the cancelled command text is still there, in the previous block
417+
.then(_ => _.getText())
418+
.then(input => assert.strictEqual(input, cmd))
419+
)
420+
.catch(Common.oops(this, true))
421+
}

plugins/plugin-bash-like/src/test/bash-like/ctrl-c.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { strictEqual } from 'assert'
1817
import { Common, CLI, Keys, ReplExpect, Selectors, Util } from '@kui-shell/test'
1918

20-
function doCancel(this: Common.ISuite, cmd = '') {
21-
return this.app.client
22-
.$(Selectors.CURRENT_PROMPT_BLOCK)
23-
.then(async _ => {
24-
_.waitForExist()
25-
return _.getAttribute('data-input-count')
26-
})
27-
.then(count => parseInt(count, 10))
28-
.then(count =>
29-
this.app.client
30-
.keys(cmd)
31-
.then(() => this.app.client.keys(Keys.ctrlC))
32-
.then(() => ({ app: this.app, count: count }))
33-
.then(ReplExpect.blank)
34-
.then(() => this.app.client.$(Selectors.PROMPT_N(count))) // make sure the cancelled command text is still there, in the previous block
35-
.then(_ => _.getText())
36-
.then(input => strictEqual(input, cmd))
37-
)
38-
.catch(Common.oops(this, true))
39-
}
40-
4119
function doClear(this: Common.ISuite) {
4220
it('should clear the terminal', () =>
4321
CLI.command('clear', this.app).then(() => ReplExpect.consoleToBeClear(this.app)))
@@ -50,7 +28,7 @@ describe(`Cancel via Ctrl+C then clear then execute ${process.env.MOCHA_RUN_TARG
5028
after(Common.after(this))
5129

5230
const clear = doClear.bind(this)
53-
const cancel = doCancel.bind(this)
31+
const cancel = Util.doCancel.bind(this)
5432

5533
it('should type a command, but hit ctrl+c before executing it', () => cancel('echo XXXXXXXXXX'))
5634
clear()
@@ -66,7 +44,7 @@ describe(`Cancel via Ctrl+C ${process.env.MOCHA_RUN_TARGET || ''}`, function(thi
6644
after(Common.after(this))
6745

6846
const clear = doClear.bind(this)
69-
const cancel = doCancel.bind(this)
47+
const cancel = Util.doCancel.bind(this)
7048

7149
it('should hit ctrl+c', () => cancel())
7250
clear()

plugins/plugin-bash-like/src/test/bash-like/pty-copy-paste.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,28 +91,28 @@ describe(`xterm copy paste ${process.env.MOCHA_RUN_TARGET || ''}`, function(this
9191
await Common.refresh(this)
9292

9393
// emit some characters to the current prompt
94-
console.error('CP2')
95-
await this.app.client.keys(text)
94+
const emittedText = 'hello'
95+
const res1 = await CLI.command(`echo ${emittedText}`, this.app)
9696

97-
// wait for those characters to appear in the prompt
98-
console.error('CP3')
97+
// wait for the output to appear
98+
await this.app.client.$(rows(res1.count)).then(_ => _.waitForExist())
99+
100+
let idx1 = 0
99101
await this.app.client.waitUntil(
100102
async () => {
101-
const actualText = await this.app.client.$(Selectors.CURRENT_PROMPT).then(_ => _.getValue())
102-
return actualText === text
103+
const actualText = await this.app.client.$(rows(res1.count)).then(_ => _.getText())
104+
if (++idx1 > 5) {
105+
console.error('still waiting for emitted text', actualText, res1.count)
106+
}
107+
return actualText === emittedText
103108
},
104109
{ timeout: CLI.waitTimeout }
105110
)
106111

107-
// copy the content of the current prompt
108-
console.error('CP4')
109-
await this.app.client.$(Selectors.CURRENT_PROMPT).then(_ => _.doubleClick())
110-
console.error('CP5')
111-
await this.app.client.execute(() => document.execCommand('copy'))
112+
console.log('now should copy from xterm output and paste inside of xterm')
112113

113-
// cancel out the current prompt so we can execute vi
114-
console.error('CP6')
115-
await this.app.client.keys(Keys.ctrlC)
114+
await this.app.client.$(firstRow(res1.count)).then(_ => _.doubleClick())
115+
await this.app.client.execute(() => document.execCommand('copy'))
116116

117117
// open vi, so we have an xterm to receive a paste event
118118
// the last true means don't try to use the copy-paste optimization

plugins/plugin-client-common/src/components/Views/Terminal/ClipboardTransfer.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import { v4 } from 'uuid'
1818
import { eventBus, CommandStartEvent, CommandCompleteEvent, ScalarResponse, flatten, getTabId } from '@kui-shell/core'
1919
import ScrollableTerminal, { getSelectionText } from './ScrollableTerminal'
2020
import { isNotebookImpl } from './Snapshot'
21-
import { CompleteBlock } from './Block/BlockModel'
21+
import { FinishedBlock, hasStartEvent, isWithCompleteEvent } from './Block/BlockModel'
2222

2323
interface ClipboardTransfer {
2424
apiVersion: 'kui-shell/v1'
2525
kind: 'ClipboardTransfer'
26-
blocks: CompleteBlock[]
26+
blocks: FinishedBlock[]
2727
}
2828

2929
function isClipboardTransfer(transfer: Record<string, any>): transfer is ClipboardTransfer {
@@ -99,15 +99,21 @@ export function onPaste(this: ScrollableTerminal, evt: ClipboardEvent) {
9999
const { uuid, facade } = this.state.splits[target.scrollbackIdx]
100100

101101
// update the events to retarget them to our target split
102-
const { startEvent, completeEvent } = transfer.blocks[0]
103102
const execUUID = v4()
104103
const retarget = { execUUID, tab: facade }
105-
const start = Object.assign(startEvent, retarget)
106-
const complete = Object.assign(completeEvent, retarget) as CommandCompleteEvent<ScalarResponse>
107104

108105
// finally, fire the events off
109-
this.onExecStart(uuid, false, start, target.insertionIdx)
110-
this.onExecEnd(uuid, false, complete, target.insertionIdx)
106+
if (hasStartEvent(transfer.blocks[0])) {
107+
const { startEvent } = transfer.blocks[0]
108+
const start = Object.assign(startEvent, retarget)
109+
this.onExecStart(uuid, false, start, target.insertionIdx)
110+
}
111+
112+
if (isWithCompleteEvent(transfer.blocks[0])) {
113+
const { completeEvent } = transfer.blocks[0]
114+
const complete = Object.assign(completeEvent, retarget) as CommandCompleteEvent<ScalarResponse>
115+
this.onExecEnd(uuid, false, complete, target.insertionIdx)
116+
}
111117
}
112118
} catch (err) {
113119
// console.error(err)

plugins/plugin-client-common/src/components/Views/Terminal/ScrollableTerminal.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
Processing,
6666
isActive,
6767
isAnnouncement,
68+
isEmpty,
6869
isSectionBreak,
6970
isWithCompleteEvent,
7071
isOk,
@@ -240,8 +241,13 @@ export default class ScrollableTerminal extends React.PureComponent<Props, State
240241
const onSnapshot = async (evt: SnapshotRequestEvent) => {
241242
const splits = this.state.splits.map(({ inverseColors, blocks, uuid }) => {
242243
const { filter = () => true } = evt
244+
245+
const isComplete = (block: BlockModel) => {
246+
return hasStartEvent(block) && filter(block.startEvent) && block.startEvent.route !== '/split'
247+
}
248+
243249
const snapshotBlocks = blocks
244-
.filter(_ => hasStartEvent(_) && filter(_.startEvent) && _.startEvent.route !== '/split')
250+
.filter(_ => isComplete(_) || isEmpty(_) || isCancelled(_))
245251
.map(snapshot)
246252
.filter(_ => _)
247253

plugins/plugin-client-common/src/components/Views/Terminal/Snapshot.ts

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,24 @@ import {
3030
isMultiModalResponse
3131
} from '@kui-shell/core'
3232

33-
import { CompleteBlock, isAnnouncement, isOk, isOops } from './Block/BlockModel'
33+
import {
34+
FinishedBlock,
35+
hasStartEvent,
36+
isAnnouncement,
37+
isCancelled,
38+
isEmpty,
39+
isOk,
40+
isOops,
41+
isWithCompleteEvent
42+
} from './Block/BlockModel'
3443

3544
/**
3645
* Split: captures the split uuid and blocks in a split
3746
*
3847
*/
3948
export type Split = {
4049
uuid: string
41-
blocks: CompleteBlock[]
50+
blocks: FinishedBlock[]
4251
inverseColors?: boolean
4352
}
4453

@@ -55,7 +64,7 @@ export function isNotebookImpl(raw: Record<string, any>): raw is NotebookImpl {
5564
return isNotebook(model) && model.spec && Array.isArray(model.spec.splits)
5665
}
5766

58-
export function snapshot(block: CompleteBlock): CompleteBlock {
67+
export function snapshot(block: FinishedBlock): FinishedBlock {
5968
if (!isAnnouncement(block) && (isOops(block) || isOk(block))) {
6069
const execOptions = Object.assign(
6170
{},
@@ -103,6 +112,34 @@ export function snapshot(block: CompleteBlock): CompleteBlock {
103112
startEvent,
104113
completeEvent
105114
})
115+
} else if (isEmpty(block)) {
116+
const execOptions = Object.assign(
117+
{},
118+
block.completeEvent.execOptions,
119+
{ block: undefined },
120+
{
121+
tab:
122+
block.completeEvent.execOptions && block.completeEvent.execOptions.tab
123+
? block.completeEvent.execOptions.tab.uuid
124+
: undefined
125+
}
126+
)
127+
128+
const evaluatorOptions = Object.assign({}, block.completeEvent.evaluatorOptions, {
129+
usage: undefined,
130+
flags: undefined
131+
})
132+
133+
const tab = block.completeEvent.tab ? block.completeEvent.tab.uuid : undefined
134+
135+
const completeEvent = Object.assign({}, block.completeEvent, { execOptions, evaluatorOptions }, { tab })
136+
137+
return Object.assign(block, {
138+
isReplay: true,
139+
completeEvent
140+
})
141+
} else if (isCancelled(block)) {
142+
return Object.assign(block, { isReplay: true })
106143
}
107144
}
108145

@@ -111,7 +148,7 @@ export function allocateTab(target: CommandStartEvent | CommandCompleteEvent, ta
111148
}
112149

113150
/** assign tab to the block */
114-
export function tabAlignment(block: CompleteBlock, tab: Tab): CompleteBlock {
151+
export function tabAlignment(block: FinishedBlock, tab: Tab): FinishedBlock {
115152
const allocateTabForTable = (table: Table) => {
116153
table.body.forEach(row => {
117154
const onclickHome = row.onclick ? row : row.attributes.find(_ => _.onclick && _.key === 'NAME')
@@ -122,23 +159,28 @@ export function tabAlignment(block: CompleteBlock, tab: Tab): CompleteBlock {
122159
})
123160
}
124161

125-
if (isMultiModalResponse(block.completeEvent.response)) {
126-
block.completeEvent.response.modes.forEach(mode => {
127-
if (isScalarContent(mode)) {
128-
if (isTable(mode.content)) {
129-
allocateTabForTable(mode.content)
162+
if (isWithCompleteEvent(block)) {
163+
if (isMultiModalResponse(block.completeEvent.response)) {
164+
block.completeEvent.response.modes.forEach(mode => {
165+
if (isScalarContent(mode)) {
166+
if (isTable(mode.content)) {
167+
allocateTabForTable(mode.content)
168+
}
130169
}
131-
}
132-
})
170+
})
133171

134-
block.response = block.completeEvent.response
135-
} else if (isTable(block.completeEvent.response)) {
136-
allocateTabForTable(block.completeEvent.response)
137-
block.response = block.completeEvent.response
172+
block.response = block.completeEvent.response
173+
} else if (isTable(block.completeEvent.response)) {
174+
allocateTabForTable(block.completeEvent.response)
175+
block.response = block.completeEvent.response
176+
}
177+
178+
allocateTab(block.completeEvent, tab)
138179
}
139180

140-
allocateTab(block.completeEvent, tab)
141-
allocateTab(block.startEvent, tab)
181+
if (hasStartEvent(block)) {
182+
allocateTab(block.startEvent, tab)
183+
}
142184

143185
return block
144186
}
@@ -194,18 +236,20 @@ export class FlightRecorder {
194236
this.splits.map(split =>
195237
Promise.all(
196238
split.blocks.map(async _ => {
197-
if (isMultiModalResponse(_.completeEvent.response)) {
198-
await Promise.all(
199-
_.completeEvent.response.modes.map(async mode => {
200-
if (isScalarContent(mode)) {
201-
if (isTable(mode.content)) {
202-
await this.recordTable(mode.content)
239+
if (isWithCompleteEvent(_)) {
240+
if (isMultiModalResponse(_.completeEvent.response)) {
241+
await Promise.all(
242+
_.completeEvent.response.modes.map(async mode => {
243+
if (isScalarContent(mode)) {
244+
if (isTable(mode.content)) {
245+
await this.recordTable(mode.content)
246+
}
203247
}
204-
}
205-
})
206-
)
207-
} else if (isTable(_.completeEvent.response)) {
208-
await this.recordTable(_.completeEvent.response)
248+
})
249+
)
250+
} else if (isTable(_.completeEvent.response)) {
251+
await this.recordTable(_.completeEvent.response)
252+
}
209253
}
210254
})
211255
)

0 commit comments

Comments
 (0)