Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.0.0"
".": "2.0.0"
}
26 changes: 1 addition & 25 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,7 @@ jobs:
if: needs.check-skip.outputs.should_skip != 'true'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Retrieve version from package.json
id: retrieve_version
run: |
new_version=$(jq -r .version package.json)
if [ -z "$new_version" ]; then
echo "❌ version not found in package.json" >&2
exit 1
fi
if ! echo "$new_version" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' > /dev/null; then
echo "❌ version '$new_version' is not a valid semantic version (e.g., 1.2.3)" >&2
exit 1
fi
echo "new_version=$new_version" >> $GITHUB_OUTPUT

- name: Set release version for changelog
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git commit --allow-empty -m "chore: release ${{ steps.retrieve_version.outputs.new_version }}" -m "Release-As: ${{ steps.retrieve_version.outputs.new_version }}"
git push

- name: Create release changelog
- name: Create release changelog & update version
id: release
uses: googleapis/release-please-action@v4
with:
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/release-please-config.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"plugins": ["node-workspace", "sentence-case"],
"plugins": ["sentence-case"],
"always-update": true,
"skip-github-release": true,
"pull-request-title-pattern": "chore: bump version to ${version} and update changelog",
"pull-request-header": "🤖: I have generated a release changelog",
"pull-request-footer": "This PR was automatically generated by 🤖.",
"include-component-in-tag": false,
"packages": {
".": {
"release-type": "simple",
"release-type": "node",
"changelog-sections": [
{
"type": "feat",
Expand Down Expand Up @@ -50,6 +51,12 @@
"section": "Miscellaneous",
"hidden": false,
"type-plural": "Chores"
},
{
"type": "ci",
"section": "CI",
"hidden": false,
"type-plural": "CI"
}
]
}
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## [2.0.0](https://github.com/pyx-industries/vc-render-template-utils/compare/1.0.0...v2.0.0) (2026-05-01)


### ⚠ BREAKING CHANGES

* **render-method-2024:** align RenderTemplate2024 with downstream renderer ([#19](https://github.com/pyx-industries/vc-render-template-utils/issues/19))

### Features

* Enhance removeLineBreaks to handle \r and consecutive line breaks ([#5](https://github.com/pyx-industries/vc-render-template-utils/issues/5)) ([4d14aaa](https://github.com/pyx-industries/vc-render-template-utils/commit/4d14aaa3db0c627c2e475bab69e9cea79edb065e))
* Normalise template whitespace ([#4](https://github.com/pyx-industries/vc-render-template-utils/issues/4)) ([fb40692](https://github.com/pyx-industries/vc-render-template-utils/commit/fb40692c6a74f02aedae454a31498b79fc611608))
* **render-method-2024:** Align RenderTemplate2024 with downstream renderer ([#19](https://github.com/pyx-industries/vc-render-template-utils/issues/19)) ([d042ef9](https://github.com/pyx-industries/vc-render-template-utils/commit/d042ef9afbbea4fca20bbeb5d51d4c1515a69faa))


### Miscellaneous

* Add repository information ([318eeed](https://github.com/pyx-industries/vc-render-template-utils/commit/318eeed09984066db8fa7335652b6db87c2e36b3))
* Fix extractRenderTemplate example format ([#3](https://github.com/pyx-industries/vc-render-template-utils/issues/3)) ([1212afa](https://github.com/pyx-industries/vc-render-template-utils/commit/1212afa2b73ffa0c49e80df799a9a124d23c8a7a))


### CI

* Fix changelog manifest version bump ([#11](https://github.com/pyx-industries/vc-render-template-utils/issues/11)) ([4b5d4d9](https://github.com/pyx-industries/vc-render-template-utils/commit/4b5d4d984ef5efcb1aa36f03b57dd310077493b8))
* Remove node workspace plugin ([#15](https://github.com/pyx-industries/vc-render-template-utils/issues/15)) ([b444c9a](https://github.com/pyx-industries/vc-render-template-utils/commit/b444c9ac435d2d8e8c16f428df2410967ccd53df))
* Update changelog config ([#7](https://github.com/pyx-industries/vc-render-template-utils/issues/7)) ([f6bdf4b](https://github.com/pyx-industries/vc-render-template-utils/commit/f6bdf4b35c62e99cddf0821466ccffe9268b44dc))
* Update changelog config ([#9](https://github.com/pyx-industries/vc-render-template-utils/issues/9)) ([f48a248](https://github.com/pyx-industries/vc-render-template-utils/commit/f48a24881342bdec3773e095eb569e808535d4dd))

## [1.0.0](https://github.com/pyx-industries/vc-render-template-utils/compare/v1.0.0...v1.0.0) (2025-04-29)


Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ Constructs a render method object for the specified template and type.

- `template`: The template string or empty if using a URL.
- `renderMethodType`: Either `RenderTemplate2024` or `WebRenderingTemplate2022`.
- `extra`: Optional metadata (e.g., `url` or `mediaQuery`).
- `extra`: Optional metadata. For `RenderTemplate2024` the supported keys are `name`, `mediaQuery`, `url`, `mediaType`, and `digestMultibase`. Empty or non-string values are omitted from the constructed render method rather than emitted as empty strings.

### extractRenderTemplate

```typescript
`extractRenderTemplate(renderMethod: RenderMethod)
extractRenderTemplate(renderMethod: RenderMethod)
```

Extracts the template content, fetching from a URL if necessary.
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"name": "@pyx-industries/vc-render-template-utils",
"version": "1.0.0",
"version": "2.0.0",
"description": "A lightweight utility library for constructing, extracting and rendering verifiable credential render templates.",
"repository": {
"type": "git",
"url": "https://github.com/pyx-industries/vc-render-template-utils.git"
},
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
87 changes: 75 additions & 12 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Main Index Functions', () => {
MockedRenderMethodFactory.mockClear();
MockedTemplatingEngineFactory.mockClear();
mockedUtils.removeLineBreaks.mockClear();
mockedUtils.normaliseWhitespace.mockClear();

mockRenderMethodProvider = {
construct: jest.fn(),
Expand All @@ -51,27 +52,39 @@ describe('Main Index Functions', () => {
});

describe('constructRenderMethod', () => {
const template = 'template\nwith\nbreaks';
const cleanedTemplate = 'templatewithbreaks';
const template = 'template\nwith\nbreaks and spaces';
const templateWithoutLineBreaks = 'templatewithbreaks and spaces';
const cleanedTemplate = 'templatewithbreaks and spaces';
const type = RenderMethodType.RenderTemplate2024;
const extra = { key: 'value' };
const expectedResult: RenderMethod = {
type: RenderMethodType.RenderTemplate2024,
type: [RenderMethodType.RenderTemplate2024],
template: cleanedTemplate,
mediaQuery: '',
url: '',
};

it('should call removeLineBreaks with the template', () => {
mockedUtils.removeLineBreaks.mockReturnValue(cleanedTemplate);
mockedUtils.removeLineBreaks.mockReturnValue(templateWithoutLineBreaks);
mockedUtils.normaliseWhitespace.mockReturnValue(cleanedTemplate);
mockRenderMethodProvider.construct.mockReturnValue(expectedResult);

constructRenderMethod(template, type, extra);
expect(mockedUtils.removeLineBreaks).toHaveBeenCalledWith(template);
});

it('should call normaliseWhitespace with the result of removeLineBreaks', () => {
mockedUtils.removeLineBreaks.mockReturnValue(templateWithoutLineBreaks);
mockedUtils.normaliseWhitespace.mockReturnValue(cleanedTemplate);
mockRenderMethodProvider.construct.mockReturnValue(expectedResult);

constructRenderMethod(template, type, extra);
expect(mockedUtils.normaliseWhitespace).toHaveBeenCalledWith(
templateWithoutLineBreaks,
);
});

it('should create RenderMethodFactory and call createRenderMethod', () => {
mockedUtils.removeLineBreaks.mockReturnValue(cleanedTemplate);
mockedUtils.removeLineBreaks.mockReturnValue(templateWithoutLineBreaks);
mockedUtils.normaliseWhitespace.mockReturnValue(cleanedTemplate);
mockRenderMethodProvider.construct.mockReturnValue(expectedResult);

constructRenderMethod(template, type, extra);
Expand All @@ -82,7 +95,8 @@ describe('Main Index Functions', () => {
});

it('should call construct on the created render method provider', () => {
mockedUtils.removeLineBreaks.mockReturnValue(cleanedTemplate);
mockedUtils.removeLineBreaks.mockReturnValue(templateWithoutLineBreaks);
mockedUtils.normaliseWhitespace.mockReturnValue(cleanedTemplate);
mockRenderMethodProvider.construct.mockReturnValue(expectedResult);

constructRenderMethod(template, type, extra);
Expand All @@ -93,7 +107,8 @@ describe('Main Index Functions', () => {
});

it('should return the result from the render method provider construct', () => {
mockedUtils.removeLineBreaks.mockReturnValue(cleanedTemplate);
mockedUtils.removeLineBreaks.mockReturnValue(templateWithoutLineBreaks);
mockedUtils.normaliseWhitespace.mockReturnValue(cleanedTemplate);
mockRenderMethodProvider.construct.mockReturnValue(expectedResult);

const result = constructRenderMethod(template, type, extra);
Expand All @@ -103,12 +118,12 @@ describe('Main Index Functions', () => {

describe('extractRenderTemplate', () => {
const renderMethodObject: RenderMethod = {
type: RenderMethodType.RenderTemplate2024,
type: [RenderMethodType.RenderTemplate2024],
template: 'some template',
};
const expectedTemplate = 'extracted template content';

it('should create RenderMethodFactory and call createRenderMethod', async () => {
it('should create RenderMethodFactory and call createRenderMethod with the resolved render method type', async () => {
mockRenderMethodProvider.extractTemplate.mockResolvedValue(
expectedTemplate,
);
Expand All @@ -117,7 +132,55 @@ describe('Main Index Functions', () => {
expect(MockedRenderMethodFactory).toHaveBeenCalledTimes(1);
expect(
MockedRenderMethodFactory.prototype.createRenderMethod,
).toHaveBeenCalledWith(renderMethodObject.type);
).toHaveBeenCalledWith(RenderMethodType.RenderTemplate2024);
});

it('should resolve the render method type from a `type` array containing additional values', async () => {
mockRenderMethodProvider.extractTemplate.mockResolvedValue(
expectedTemplate,
);

await extractRenderTemplate({
type: ['SomeOtherType', RenderMethodType.RenderTemplate2024],
template: 'some template',
});
expect(
MockedRenderMethodFactory.prototype.createRenderMethod,
).toHaveBeenCalledWith(RenderMethodType.RenderTemplate2024);
});

it('should pass a non-array `type` through unchanged to the factory', async () => {
mockRenderMethodProvider.extractTemplate.mockResolvedValue(
expectedTemplate,
);

await extractRenderTemplate({
type: RenderMethodType.WebRenderingTemplate2022,
template: 'some template',
});
expect(
MockedRenderMethodFactory.prototype.createRenderMethod,
).toHaveBeenCalledWith(RenderMethodType.WebRenderingTemplate2022);
});

it('should throw UnsupportedRenderMethodError including the unsupported entries when no `type` entry is supported', async () => {
await expect(
extractRenderTemplate({
type: ['UnknownType', 'AnotherUnknown'],
template: 'some template',
} as unknown as RenderMethod),
).rejects.toThrow(
'Unsupported render method: UnknownType, AnotherUnknown',
);
});

it('should throw UnsupportedRenderMethodError with an `<empty>` indicator when `type` is an empty array', async () => {
await expect(
extractRenderTemplate({
type: [],
template: 'some template',
} as unknown as RenderMethod),
).rejects.toThrow('Unsupported render method: <empty>');
});

it('should call extractTemplate on the created render method provider', async () => {
Expand Down
69 changes: 65 additions & 4 deletions src/__tests__/render_methods/render-method-2024.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,84 @@ describe('RenderMethod2024', () => {
});

describe('construct', () => {
it('should construct a RenderTemplate2024 object correctly', () => {
it('should construct a RenderTemplate2024 object with `type` as an array containing the render method type', () => {
const result = renderMethod.construct(templateContent, extraData);
expect(result.type).toEqual([RenderMethodType.RenderTemplate2024]);
});

it('should include template and provided extra fields when present', () => {
const result = renderMethod.construct(templateContent, extraData);
expect(result).toEqual({
type: RenderMethodType.RenderTemplate2024,
type: [RenderMethodType.RenderTemplate2024],
template: templateContent,
mediaQuery: extraData.mediaQuery,
url: extraData.url,
});
});

it('should use default values if extra data is missing', () => {
it('should accept all spec-defined optional fields via extra', () => {
const fullExtra = {
name: 'Display Name',
mediaQuery: 'print',
url: 'http://example.com/t.html',
mediaType: 'text/html',
digestMultibase: 'zQmExampleHash',
};
const result = renderMethod.construct(templateContent, fullExtra);
expect(result).toEqual({
type: [RenderMethodType.RenderTemplate2024],
template: templateContent,
...fullExtra,
});
});

it('should omit optional fields that are not provided rather than emitting empty strings', () => {
const result = renderMethod.construct(templateContent, {});
expect(result).toEqual({
type: RenderMethodType.RenderTemplate2024,
type: [RenderMethodType.RenderTemplate2024],
template: templateContent,
});
expect(result).not.toHaveProperty('mediaQuery');
expect(result).not.toHaveProperty('url');
expect(result).not.toHaveProperty('name');
expect(result).not.toHaveProperty('mediaType');
expect(result).not.toHaveProperty('digestMultibase');
});

it('should omit optional fields that are explicitly empty strings', () => {
const result = renderMethod.construct(templateContent, {
name: '',
mediaQuery: '',
url: '',
mediaType: '',
digestMultibase: '',
});
expect(result).toEqual({
type: [RenderMethodType.RenderTemplate2024],
template: templateContent,
});
});

it('should omit `template` when it is an empty string (URL-only case)', () => {
const result = renderMethod.construct('', {
url: 'http://example.com/t.html',
});
expect(result).toEqual({
type: [RenderMethodType.RenderTemplate2024],
url: 'http://example.com/t.html',
});
expect(result).not.toHaveProperty('template');
});

it('should ignore non-string values supplied via extra', () => {
const result = renderMethod.construct(templateContent, {
name: 42,
mediaQuery: null,
url: undefined,
} as unknown as Record<string, unknown>);
expect(result).toEqual({
type: [RenderMethodType.RenderTemplate2024],
template: templateContent,
});
});
});
Expand Down
Loading
Loading