Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(facade): add FWorksheet.getRange(a1Notation) #3504

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 104 additions & 5 deletions packages/facade/src/apis/sheets/__tests__/f-range.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@

/* eslint-disable ts/no-non-null-asserted-optional-chain */

import type { ICellData, Injector, IRange, IStyleData, Nullable } from '@univerjs/core';
import { DataValidationType, HorizontalAlign, ICommandService, IUniverInstanceService, VerticalAlign, WrapStrategy } from '@univerjs/core';
import { AddWorksheetMergeCommand, SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand } from '@univerjs/sheets';
import { beforeEach, describe, expect, it } from 'vitest';

import { FormulaDataModel } from '@univerjs/engine-formula';
import { AddWorksheetMergeCommand, SetHorizontalTextAlignCommand, SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand, SetTextWrapCommand, SetVerticalTextAlignCommand } from '@univerjs/sheets';
import { AddSheetDataValidationCommand } from '@univerjs/sheets-data-validation';

import { ClearSheetsFilterCriteriaCommand, RemoveSheetFilterCommand, SetSheetFilterRangeCommand, SetSheetsFilterCriteriaCommand } from '@univerjs/sheets-filter-ui';
import { FUniver } from '../../facade';
import { beforeEach, describe, expect, it } from 'vitest';
import type { ICellData, Injector, IRange, IStyleData, Nullable } from '@univerjs/core';
import { createFacadeTestBed } from '../../__tests__/create-test-bed';
import { FUniver } from '../../facade';

describe('Test FRange', () => {
let get: Injector['get'];
Expand Down Expand Up @@ -715,4 +715,103 @@ describe('Test FRange', () => {
expect(hasError).toBeTruthy();
});
//#endregion

// Add these new test cases
it('Range getRow, getColumn, getWidth, getHeight with A1 notation', () => {
const activeSheet = univerAPI.getActiveWorkbook()?.getActiveSheet();

// Test range with A1 notation
const rangeA1D5 = activeSheet?.getRange('A1:D5');
expect(rangeA1D5?.getRow()).toBe(0);
expect(rangeA1D5?.getColumn()).toBe(0);
expect(rangeA1D5?.getWidth()).toBe(4);
expect(rangeA1D5?.getHeight()).toBe(5);

// Test single cell with A1 notation
const rangeB3 = activeSheet?.getRange('B3');
expect(rangeB3?.getRow()).toBe(2);
expect(rangeB3?.getColumn()).toBe(1);
expect(rangeB3?.getWidth()).toBe(1);
expect(rangeB3?.getHeight()).toBe(1);

// Test range with only column letters
const rangeAD = activeSheet?.getRange('A:D');
expect(rangeAD?.getRow()).toBe(0);
expect(rangeAD?.getColumn()).toBe(0);
expect(rangeAD?.getWidth()).toBe(4);
expect(rangeAD?.getHeight()).toBe(100);

// Test range with only row numbers
const range15 = activeSheet?.getRange('1:5');
expect(range15?.getRow()).toBe(0);
expect(range15?.getColumn()).toBe(0);
// Width should be the maximum number of columns in the sheet
expect(range15?.getWidth()).toBeGreaterThan(0);
expect(range15?.getHeight()).toBe(5);

// Test with sheet name
const rangeWithSheet = activeSheet?.getRange('sheet1!E6:G8');
expect(rangeWithSheet?.getRow()).toBe(5);
expect(rangeWithSheet?.getColumn()).toBe(4);
expect(rangeWithSheet?.getWidth()).toBe(3);
expect(rangeWithSheet?.getHeight()).toBe(3);

// Test case insensitivity
const rangeCaseInsensitive = activeSheet?.getRange('h10:J12');
expect(rangeCaseInsensitive?.getRow()).toBe(9);
expect(rangeCaseInsensitive?.getColumn()).toBe(7);
expect(rangeCaseInsensitive?.getWidth()).toBe(3);
expect(rangeCaseInsensitive?.getHeight()).toBe(3);

// Test invalid range
expect(() => activeSheet?.getRange('sheet_not_exist!A1:D5').getValues()).toThrow();
});

it('Range getValues with A1 notation', () => {
const activeSheet = univerAPI.getActiveWorkbook()?.getActiveSheet();

// Set up some test data
activeSheet?.getRange(0, 0, 5, 4)?.setValues([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
]);

// Test single cell
expect(activeSheet?.getRange('A1').getValues()).toEqual([[1]]);

// Test range
expect(activeSheet?.getRange('A1:D5').getValues()).toEqual([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
]);

// Test case insensitivity
expect(activeSheet?.getRange('a1:d5').getValues()).toEqual([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
]);

// Test with sheet name
expect(activeSheet?.getRange('sheet1!A1:D5').getValues()).toEqual([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
]);

// Test out of bounds range
expect(activeSheet?.getRange('A1:Z100').getValues()).toEqual(expect.arrayContaining([
expect.arrayContaining([1, 2, 3, 4, null, null, null, null, null, null]),
]));
});
});
88 changes: 77 additions & 11 deletions packages/facade/src/apis/sheets/f-worksheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
*/

import { Direction, ICommandService, Inject, Injector, RANGE_TYPE } from '@univerjs/core';
import { deserializeRangeWithSheet } from '@univerjs/engine-formula';
import { copyRangeStyles, InsertColCommand, InsertRowCommand, MoveColsCommand, MoveRowsCommand, RemoveColCommand, RemoveRowCommand, SetColHiddenCommand, SetColWidthCommand, SetRowHeightCommand, SetRowHiddenCommand, SetSpecificColsVisibleCommand, SetSpecificRowsVisibleCommand, SetWorksheetRowIsAutoHeightCommand, SheetsSelectionsService } from '@univerjs/sheets';
import { DataValidationModel, SheetsDataValidationValidatorService } from '@univerjs/sheets-data-validation';

import { DataValidationModel, SheetsDataValidationValidatorService } from '@univerjs/sheets-data-validation';
import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
import { SheetsFilterService } from '@univerjs/sheets-filter';
import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
Expand Down Expand Up @@ -86,17 +87,82 @@ export class FWorksheet {
return this._injector.createInstance(FSelection, this._workbook, this._worksheet, selections);
}

getRange(row: number, col: number, height?: number, width?: number): FRange {
const range: IRange = {
startRow: row,
endRow: row + (height ?? 1) - 1,
startColumn: col,
endColumn: col + (width ?? 1) - 1,
unitId: this._workbook.getUnitId(),
sheetId: this._worksheet.getSheetId(),
};
/**
* Returns a Range object representing a single cell at the specified row and column.
* @param row The row index of the cell.
* @param column The column index of the cell.
* @returns A Range object representing the specified cell.
*/
getRange(row: number, column: number): FRange;

/**
* Returns a Range object representing a range starting at the specified row and column, with the specified number of rows.
* @param row The starting row index of the range.
* @param column The starting column index of the range.
* @param numRows The number of rows in the range.
* @returns A Range object representing the specified range.
*/
getRange(row: number, column: number, numRows: number): FRange;

/**
* Returns a Range object representing a range starting at the specified row and column, with the specified number of rows and columns.
* @param row The starting row index of the range.
* @param column The starting column index of the range.
* @param numRows The number of rows in the range.
* @param numColumns The number of columns in the range.
* @returns A Range object representing the specified range.
*/
getRange(row: number, column: number, numRows: number, numColumns: number): FRange;

/**
* Returns a Range object specified by A1 notation.
* @param a1Notation A string representing a range in A1 notation.
* @returns A Range object representing the specified range.
*/
getRange(a1Notation: string): FRange;

getRange(rowOrA1Notation: number | string, column?: number, numRows?: number, numColumns?: number): FRange {
let range: IRange;
let sheet: Worksheet;

if (typeof rowOrA1Notation === 'string') {
// A1 notation
const { range: parsedRange, sheetName } = deserializeRangeWithSheet(rowOrA1Notation);

const rangeSheet = sheetName ? this._workbook.getSheetBySheetName(sheetName) : this._worksheet;
if (!rangeSheet) {
throw new Error('Range not found');
}
sheet = rangeSheet;

range = {
...parsedRange,
unitId: this._workbook.getUnitId(),
sheetId: sheet.getSheetId(),
// Use the current range instead of the future actual range to match Apps Script behavior.
// Users can create the latest range in real time when needed.
rangeType: RANGE_TYPE.NORMAL,
startRow: parsedRange.rangeType === RANGE_TYPE.COLUMN ? 0 : parsedRange.startRow,
endRow: parsedRange.rangeType === RANGE_TYPE.COLUMN ? sheet.getMaxRows() - 1 : parsedRange.endRow,
startColumn: parsedRange.rangeType === RANGE_TYPE.ROW ? 0 : parsedRange.startColumn,
endColumn: parsedRange.rangeType === RANGE_TYPE.ROW ? sheet.getMaxColumns() - 1 : parsedRange.endColumn,
};
} else if (typeof rowOrA1Notation === 'number' && column !== undefined) {
sheet = this._worksheet;
// Range
range = {
startRow: rowOrA1Notation,
endRow: rowOrA1Notation + (numRows ?? 1) - 1,
startColumn: column,
endColumn: column + (numColumns ?? 1) - 1,
unitId: this._workbook.getUnitId(),
sheetId: this._worksheet.getSheetId(),
};
} else {
throw new Error('Invalid range specification');
}

return this._injector.createInstance(FRange, this._workbook, this._worksheet, range);
return this._injector.createInstance(FRange, this._workbook, sheet, range);
}

/**
Expand Down
Loading