mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-18 05:50:43 -07:00
feat(filters): Add base64_encode and base64_decode filters for Shopify compatibility (#828)
* feat(filters): add base64 encode and decode * fix: use Object.defineProperty for cross-platform btoa/atob mocking * docs(filters): update docs * docs(filters): update version
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import * as base64 from './base64-impl-browser'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
describe('base64-impl/browser', function () {
|
||||
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
|
||||
console.info('jsdom not supported, skipping base64-impl-browser...')
|
||||
return
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
const dom = new JSDOM(``, {
|
||||
url: 'https://example.com/',
|
||||
contentType: 'text/html',
|
||||
includeNodeLocations: true
|
||||
})
|
||||
|
||||
// Mock btoa and atob on global object
|
||||
Object.defineProperty(global, 'btoa', {
|
||||
value: dom.window.btoa,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(global, 'atob', {
|
||||
value: dom.window.atob,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete (global as any).btoa
|
||||
delete (global as any).atob
|
||||
})
|
||||
|
||||
describe('#base64Encode()', function () {
|
||||
it('should encode a simple string', function () {
|
||||
expect(base64.base64Encode('one two three')).toBe('b25lIHR3byB0aHJlZQ==')
|
||||
})
|
||||
|
||||
it('should encode an empty string', function () {
|
||||
expect(base64.base64Encode('')).toBe('')
|
||||
})
|
||||
|
||||
it('should encode a string with special characters', function () {
|
||||
expect(base64.base64Encode('Hello, World! @#$%')).toBe('SGVsbG8sIFdvcmxkISBAIyQl')
|
||||
})
|
||||
|
||||
it('should encode numeric strings', function () {
|
||||
expect(base64.base64Encode('123')).toBe('MTIz')
|
||||
})
|
||||
|
||||
it('should encode boolean strings', function () {
|
||||
expect(base64.base64Encode('true')).toBe('dHJ1ZQ==')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#base64Decode()', function () {
|
||||
it('should decode a simple string', function () {
|
||||
expect(base64.base64Decode('b25lIHR3byB0aHJlZQ==')).toBe('one two three')
|
||||
})
|
||||
|
||||
it('should decode an empty string', function () {
|
||||
expect(base64.base64Decode('')).toBe('')
|
||||
})
|
||||
|
||||
it('should decode a string with special characters', function () {
|
||||
expect(base64.base64Decode('SGVsbG8sIFdvcmxkISBAIyQl')).toBe('Hello, World! @#$%')
|
||||
})
|
||||
|
||||
it('should decode numeric strings', function () {
|
||||
expect(base64.base64Decode('MTIz')).toBe('123')
|
||||
})
|
||||
|
||||
it('should decode boolean strings', function () {
|
||||
expect(base64.base64Decode('dHJ1ZQ==')).toBe('true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('round-trip encoding/decoding', function () {
|
||||
it('should encode and decode back to original', function () {
|
||||
const original = 'Hello, World!'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
|
||||
it('should handle complex strings with special characters', function () {
|
||||
const original = 'Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
|
||||
it('should handle mixed unicode and ASCII', function () {
|
||||
const original = 'Hello 🌍'
|
||||
const encoded = base64.base64Encode(original)
|
||||
const decoded = base64.base64Decode(encoded)
|
||||
expect(decoded).toBe(original)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
export function base64Encode (str: string): string {
|
||||
return btoa(String.fromCharCode(...new TextEncoder().encode(str)))
|
||||
}
|
||||
|
||||
export function base64Decode (str: string): string {
|
||||
return new TextDecoder().decode(
|
||||
Uint8Array.from(atob(str), c => c.charCodeAt(0))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export function base64Encode (str: string): string {
|
||||
return Buffer.from(str, 'utf8').toString('base64')
|
||||
}
|
||||
|
||||
export function base64Decode (str: string): string {
|
||||
return Buffer.from(str, 'base64').toString('utf8')
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Base64 related filters
|
||||
*
|
||||
* Implements base64_encode and base64_decode filters for Shopify compatibility
|
||||
*/
|
||||
|
||||
import { FilterImpl } from '../template'
|
||||
import { stringify } from '../util'
|
||||
import { base64Encode, base64Decode } from './base64-impl'
|
||||
|
||||
export function base64_encode (this: FilterImpl, value: string): string {
|
||||
const str = stringify(value)
|
||||
this.context.memoryLimit.use(str.length)
|
||||
return base64Encode(str)
|
||||
}
|
||||
|
||||
export function base64_decode (this: FilterImpl, value: string): string {
|
||||
const str = stringify(value)
|
||||
this.context.memoryLimit.use(str.length)
|
||||
return base64Decode(str)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import * as urlFilters from './url'
|
||||
import * as arrayFilters from './array'
|
||||
import * as dateFilters from './date'
|
||||
import * as stringFilters from './string'
|
||||
import * as base64Filters from './base64'
|
||||
import misc from './misc'
|
||||
import { FilterImplOptions } from '../template'
|
||||
|
||||
@@ -14,5 +15,6 @@ export const filters: Record<string, FilterImplOptions> = {
|
||||
...arrayFilters,
|
||||
...dateFilters,
|
||||
...stringFilters,
|
||||
...base64Filters,
|
||||
...misc
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user