FreeReportv0.10.1English

테스트 도우미

Vitest, Jest, Playwright, Cypress로 FreeReport 화면을 테스트하는 방법: @freereportjs/testing으로 보고서 글자와 행을 읽고, 조회 조건을 넣고, 페이지를 넘기고, 디자이너를 조작합니다.

@freereportjs/testing은 사용자처럼 뷰어와 디자이너를 조작하고(조회 조건 입력, 조회, 페이지 넘기기, 찾기, 요소 선택·편집) 화면에 나온 내용을 보고서의 요소 id로 읽습니다. 내부 DOM 구조에 기대지 않아 FreeReport 버전이 바뀌어도 테스트가 그대로 동작합니다. jsdom·happy-dom(Vitest, Jest)과 실제 브라우저에서 쓸 수 있고, Playwright·Cypress용 CSS 선택자를 제공합니다.

npm i -D @freereportjs/testing

Vitest·Jest (jsdom)

jsdom은 화면 배치가 없어 페이지가 보이는 영역에 들어오지 않고 아무것도 그려지지 않습니다. mockLayout()을 한 번 부르면 요소에 크기를 주고, 모든 페이지를 보이는 것으로 처리하고, 디자이너에 필요한 브라우저 함수를 채웁니다. 되돌리는 함수를 돌려줍니다.

// npm i -D @freereportjs/testing vitest jsdom   (vitest.config: test.environment = 'jsdom')
import { beforeAll, expect, it } from 'vitest';
import { createViewer } from '@freereportjs/core';
import { mockLayout, viewerHarness } from '@freereportjs/testing';

// jsdom has no layout: without this nothing scrolls into view and no page is drawn
beforeAll(() => { mockLayout(); });

it('filters by region', async () => {
  const host = document.createElement('div');
  document.body.append(host);
  createViewer(host, { definition, fetchData: p => api.sales(p) });
  const report = viewerHarness(host);
  await report.waitForReady();

  report.setParam('region', '부산');
  report.setParam('from', new Date(2026, 9, 1));
  await report.submitParams();                     // presses 조회 and waits for the pages

  expect(report.text('title')).toBe('부산 매출 보고서');   // element id "title"
  expect(report.rows(['item', 'amount'])[0]).toEqual(['노트북', '1,200,000']);
  expect(report.overflows()).toEqual([]);         // no text cut off
});
함수돌려주는 값설명
viewerHarness(el)ViewerHarnessel 안의 뷰어용 도우미(createViewer에 준 요소, 컴포넌트를 감싼 요소, 뷰어 자체).
waitForReady(timeout?)Promise<void>지금 조회가 끝날 때까지 기다립니다. 실패하면 뷰어의 오류 문구로 reject.
state()'busy' | 'ready' | 'error'뷰어 상태.
pageCount() / currentPage() / pageText(n)number / number / string쪽수, 쪽 번호 칸의 현재 쪽, 그려진 페이지의 모든 글자.
text(id, row?) / texts(id) / hits(id)string / string[] / TextHit[]요소 id로 글자 읽기: 첫 번째, 상세 행 하나, 전부, 또는 행·페이지와 함께.
rows(ids) / column(id)string[][] / string[]상세 행을 표로(요소 id마다 한 칸), 또는 한 요소를 행 순서대로.
overflows()TextHit[]칸에 다 들어가지 않아 잘린 글자.
setParam(name, value) / paramValues() / submitParams()void / object / Promise<void>사용자처럼 조회 조건을 입력·선택하고, 패널 값을 읽고, 조회를 눌러 끝날 때까지 기다립니다.
click(act) / press(key, init?) / goTo(n) / search(text)void / void / void / string동작 이름으로 도구 모음·저장 메뉴 버튼 누르기(next, last, pdf, xlsx…), 페이지에서 키 누르기, 쪽 번호 입력, 찾기("1 / 3"을 돌려줌).

디자이너

속성은 속성 창에 보이는 이름으로 찾습니다. 디자이너를 만든 언어(lang)의 이름을 쓰세요.

import { createDesigner } from '@freereportjs/core';
import { designerHarness, mockLayout } from '@freereportjs/testing';

mockLayout();
const designer = createDesigner(host, { definition, data: { rows } });
const d = designerHarness(host);

d.select('title');                 // element id
d.setProp('글자 크기', 16);         // property by its label (lang: 'en' → 'Font size')
d.setProp('굵게', true);
d.editText('title', '월간 매출');   // double-click, type, Enter
d.select('total', { shift: true });
d.align('right');
d.press('ArrowDown', { shiftKey: true });  // 5 mm down
d.press('z', { ctrlKey: true });           // undo
d.setMode('preview');
await d.preview().waitForReady();
expect(d.preview().text('title')).toBe('월간 매출');
함수돌려주는 값설명
designerHarness(el)DesignerHarnessel 안의 디자이너용 도우미.
elementIds(band?) / elementText(id) / selected()string[] / string / string[]캔버스의 요소, 요소에 보이는 글자, 선택된 요소.
select(id, { shift }) / selectBand(key) / clearSelection()void요소 누르기(shift는 추가 선택), 밴드 제목 누르기, 빈 곳 누르기.
prop(label) / setProp(label, value) / propLabels()string / void / string[]속성 창의 이름으로 속성 읽기·바꾸기(X, 너비, 굵게, 테두리 …). 사용자가 입력하는 것과 같습니다.
editText(id, text, finish?)void텍스트·필드를 두 번 눌러 입력하고 Enter(또는 Escape).
drag(id, dx, dy) / press(key, init?)void선택한 요소를 dx, dy mm 끌기, 캔버스에서 키 누르기(Delete, 화살표, Ctrl+Z …).
align(to) / click(act)void선택한 요소 정렬(left, center, right, top, middle, bottom, sameWidth, sameHeight), 도구 모음 버튼 누르기(undo, copy, paste…).
setMode(mode) / toggleBand(key, on) / json() / applyJson(text) / preview()…탭 바꾸기, 밴드 목록, JSON 보기(applyJson은 오류 문구를 돌려줌), 미리 보기 뷰어(ViewerHarness).

Playwright

selectors.viewer와 selectors.designer는 그냥 CSS 선택자입니다. 처음 열 때와 조회할 때마다 selectors.viewer.ready를 기다리세요. 페이지를 다 그릴 때까지 뷰어는 busy 상태입니다.

import { expect, test } from '@playwright/test';
import { selectors } from '@freereportjs/testing';
const v = selectors.viewer;

test('sales report', async ({ page }) => {
  await page.goto('/reports/sales');
  await expect(page.locator(v.ready)).toBeVisible();          // the first run is done

  await page.locator(v.paramsToggle).click();
  await page.locator(v.param('region')).selectOption('부산');
  await page.locator(v.paramsSubmit).click();
  await expect(page.locator(v.ready)).toBeVisible();          // busy until the pages are drawn

  await expect(page.locator(v.text('title'))).toHaveText('부산 매출 보고서');
  await expect(page.locator(v.text('amount', 0))).toHaveText('1,200,000');  // first detail row
  await expect(page.locator(v.overflow)).toHaveCount(0);      // no text cut off

  await page.locator(v.button('last')).click();
  const [pdf] = await Promise.all([page.waitForEvent('download'), page.locator(v.button('pdf')).click()]);
  expect(pdf.suggestedFilename()).toMatch(/\.pdf$/);
});

Cypress

import { selectors } from '@freereportjs/testing';
const v = selectors.viewer;

it('sales report', () => {
  cy.visit('/reports/sales');
  cy.get(v.ready).should('exist');
  cy.get(v.paramsToggle).click();
  cy.get(v.param('region')).select('부산');
  cy.get(v.paramsSubmit).click();
  cy.get(v.ready).should('exist');
  cy.get(v.text('title')).should('have.text', '부산 매출 보고서');
  cy.get(v.overflow).should('not.exist');
});

화면에 붙는 속성

도우미가 쓰는 속성입니다. 직접 만든 스크립트에서도 쓸 수 있습니다.

선택자값설명
.fr-viewer[data-fr-state]busy | ready | error뷰어 바깥 요소. 조회를 누른 때부터 페이지를 다 그릴 때까지 busy. data-fr-run은 끝난 조회 수.
.fr-page[data-page]1, 2, …페이지 상자(1부터). 화면에 들어올 때 그립니다.
[data-fr-id]element id보고서 요소 하나로 그린 모든 것. data-fr-kind는 text, box, line, image, path.
[data-fr-row]0, 1, …상세 행 번호(정렬·필터 뒤 순서).
[data-fr-overflow]1칸에서 잘린 글자.
.fr-params [name]parameter name조회 조건 패널의 입력 칸(#fr-p-이름도 됨).
[data-act]first, next, pdf, xlsx …뷰어·디자이너의 도구 모음과 메뉴 버튼.
.fr-dz-el[data-id]element id디자이너 캔버스의 요소.