All files / views/webviews webview-utils.ts

100% Statements 7/7
75% Branches 3/4
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66                                            15x   15x 4x 4x   11x                                                                 5x 5x      
/**
 * Immutably applies a mutation callback to `record[key]`.
 *
 * - If `mutate` returns a value, the entry is updated (or inserted).
 * - If `mutate` returns `undefined`, the entry is removed.
 * - All other entries are left untouched (referential equality preserved).
 *
 * Intended for use inside a React `setState` updater to apply an optimistic
 * local-state patch immediately before posting a message to the extension
 * host, so the UI reflects the change without waiting for the async round-trip.
 *
 * @param record - The record to patch.
 * @param key - Key of the entry to mutate.
 * @param mutate - Mutator applied to the current entry value (or `undefined` when absent).
 *                 Return a new value to update/insert, or `undefined` to delete.
 * @returns A new record with the mutation applied; the input is never modified.
 */
export function patchRecord<V>(
	record: Record<string, V>,
	key: string,
	mutate: (prev: V | undefined) => V | undefined,
): Record<string, V> {
	const next = mutate(record[key]);
 
	if (next === undefined) {
		const { [key]: _, ...rest } = record;
		return rest;
	} else {
		return { ...record, [key]: next };
	}
}
 
/**
 * Immutably applies a mutation callback to `outer[outerKey][innerKey]`.
 *
 * A two-level convenience wrapper around {@link patchRecord}: the outer slice
 * is patched first, then the inner key within that slice. The outer key is
 * always kept in the result (it is never deleted), even when the inner
 * mutation produces an empty slice.
 *
 * - If `mutate` returns a value, the inner entry is updated (or inserted).
 * - If `mutate` returns `undefined`, the inner entry is removed.
 * - All other entries at both levels are left untouched (referential equality preserved).
 *
 * Intended for use inside a React `setState` updater to apply an optimistic
 * local-state patch immediately before posting a message to the extension
 * host, so the UI reflects the change without waiting for the async round-trip.
 *
 * @param outer - The two-level nested record to patch.
 * @param outerKey - Key of the first-level slice to target.
 * @param innerKey - Key of the entry within that slice to mutate.
 * @param mutate - Mutator applied to the current inner entry value (or `undefined` when absent).
 *                 Return a new value to update/insert, or `undefined` to delete.
 * @returns A new record with the mutation applied; the input is never modified.
 */
export function patchNestedRecord<V>(
	outer: Record<string, Record<string, V>>,
	outerKey: string,
	innerKey: string,
	mutate: (prev: V | undefined) => V | undefined,
): Record<string, Record<string, V>> {
	return patchRecord<Record<string, V>>(outer, outerKey, slice =>
		patchRecord(slice ?? {}, innerKey, mutate),
	);
}