Skip to content

Commit

Permalink
feat: 修复cssif问题&添加测试用例
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackgan3 committed Feb 5, 2025
1 parent c3a8262 commit e44b162
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,56 +42,36 @@ function parse (cssString) {
const ast = []
const nodeStack = []
let currentChildren = ast
function pushConditionalNode (nodeType, condition) {
// 获取父节点的 children 数组
const parentChildren = nodeStack.length > 0 ? nodeStack[nodeStack.length - 1] : ast
const node = new Node(nodeType, condition)
parentChildren.push(node)
// 入栈
nodeStack.push(parentChildren)
currentChildren = node.children
}
tokens.forEach(token => {
switch (token.type) {
case 'text': {
// 生成 Text 节点,保存代码文本
const textNode = new Node('Text')
textNode.value = token.content
currentChildren.push(textNode)
break
}
case 'if': {
pushConditionalNode('If', token.condition)
break
if (token.type === 'text') {
const node = new Node('Text')
node.value = token.content
currentChildren.push(node)
} else if (token.type === 'if') {
const node = new Node('If', token.condition)
currentChildren.push(node)
nodeStack.push(currentChildren)
currentChildren = node.children
} else if (token.type === 'elif') {
if (nodeStack.length === 0) {
throw new Error('elif without a preceding if')
}
case 'elif': {
// 处理 mpx-elif:回到 if 块的父级 children 数组
if (nodeStack.length === 0) {
throw new Error('elif without a preceding if')
}
currentChildren = nodeStack[nodeStack.length - 1]
pushConditionalNode('Elif', token.condition)
break
currentChildren = nodeStack[nodeStack.length - 1]
const node = new Node('ElseIf', token.condition)
currentChildren.push(node)
currentChildren = node.children
} else if (token.type === 'else') {
if (nodeStack.length === 0) {
throw new Error('else without a preceding if')
}
case 'else': {
if (nodeStack.length === 0) {
throw new Error('else without a preceding if')
}
currentChildren = nodeStack[nodeStack.length - 1]
pushConditionalNode('Else', null)
break
currentChildren = nodeStack[nodeStack.length - 1]
const node = new Node('Else')
currentChildren.push(node)
currentChildren = node.children
} else if (token.type === 'end') {
if (nodeStack.length > 0) {
currentChildren = nodeStack.pop()
}
case 'end': {
// 结束当前条件块,弹出上一级 children 指针
if (nodeStack.length > 0) {
currentChildren = nodeStack.pop()
} else {
throw new Error('end without matching if')
}
break
}
default:
break
}
})
return ast
Expand All @@ -112,24 +92,25 @@ function evaluateCondition (condition, defs) {

function traverseAndEvaluate (ast, defs) {
let output = ''

let batchedIf = false
function traverse (nodes) {
for (const node of nodes) {
if (node.type === 'Rule') {
if (node.type === 'Text') {
output += node.value
} else if (node.type === 'If') {
// 直接判断 If 节点
batchedIf = false
if (evaluateCondition(node.condition, defs)) {
traverse(node.children)
batchedIf = true
}
} else if (node.type === 'ElseIf') {
} else if (node.type === 'ElseIf' && !batchedIf) {
if (evaluateCondition(node.condition, defs)) {
traverse(node.children)
return
batchedIf = true
}
} else if (node.type === 'Else') {
} else if (node.type === 'Else' && !batchedIf) {
traverse(node.children)
return
}
}
}
Expand Down
198 changes: 198 additions & 0 deletions packages/webpack-plugin/test/platform/common/css-if.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
const cssIfLoader = require('../../../lib/style-compiler/strip-conditional-loader')

describe('css-if webpack loader - 测试用例', () => {
// 测试简单的 if/else 条件
it('简单条件: 当 isMobile 为 true 时,保留 if 分支内容', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true } })
}
const inputCSS = `
/*@mpx-if(isMobile)*/
.mobile { display: block; }
/*@mpx-else*/
.desktop { display: block; }
/*@mpx-end*/
`
const output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.mobile')
expect(output).not.toContain('.desktop')
})

it('简单条件: 当 isMobile 为 false 时,保留 else 分支内容', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: false } })
}
const inputCSS = `
/*@mpx-if(isMobile)*/
.mobile { display: block; }
/*@mpx-else*/
.desktop { display: block; }
/*@mpx-end*/
`
const output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.desktop')
expect(output).not.toContain('.mobile')
})

// 测试嵌套条件
it('嵌套条件: 外层 isMobile 为 true 内层 hasFeature 为 true, 输出嵌套 if 分支内容', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true, hasFeature: true } })
}
const inputCSS = `
body { margin: 0; }
/*@mpx-if(isMobile)*/
.mobile {
display: block;
/*@mpx-if(hasFeature)*/
.feature { color: red; }
/*@mpx-else*/
.feature { color: blue; }
/*@mpx-end*/
}
/*@mpx-else*/
.desktop { display: block; }
/*@mpx-end*/
header { color: red }
`
const output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.mobile')
expect(output).toContain('.feature { color: red; }')
expect(output).toContain('header { color: red }')
expect(output).not.toContain('.feature { color: blue; }')
expect(output).not.toContain('.desktop')
})

it('嵌套条件: 外层 isMobile 为 true 内层 hasFeature 为 false, 输出内层 else 分支内容', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true, hasFeature: false } })
}
const inputCSS = `
body { margin: 0; }
/*@mpx-if(isMobile)*/
.mobile {
display: block;
/*@mpx-if(hasFeature)*/
.feature { color: red; }
/*@mpx-else*/
.feature { color: blue; }
/*@mpx-end*/
}
/*@mpx-else*/
.desktop { display: block; }
/*@mpx-end*/
`
const output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.mobile')
expect(output).toContain('.feature { color: blue; }')
expect(output).not.toContain('.feature { color: red; }')
expect(output).not.toContain('.desktop')
})

// 测试多个条件分支:if、elif、else 的情况
it('多个条件分支: 优先匹配 if 分支,其次 elif,再到 else', () => {
// 测试1: isMobile 为 false,isTablet 为 true,匹配 elif 分支
let context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: false, isTablet: true } })
}
const inputCSS = `
header {}
/*@mpx-if(isMobile)*/
.mobile { display: block; }
/*@mpx-elif(isTablet)*/
.tablet { display: block; }
/*@mpx-else*/
.desktop { display: block; }
/*@mpx-end*/
body {}
`
let output = cssIfLoader.call(context, inputCSS)
expect(output).not.toContain('.mobile')
expect(output).toContain('.tablet')
expect(output).not.toContain('.desktop')
expect(output).toContain('header {}')
expect(output).toContain('body {}')

// 测试2: isMobile 与 isTablet 均为 false,匹配 else 分支
context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: false, isTablet: false } })
}
output = cssIfLoader.call(context, inputCSS)
expect(output).not.toContain('.mobile')
expect(output).not.toContain('.tablet')
expect(output).toContain('.desktop')

// 测试3: isMobile 为 true(优先匹配 if 分支),即使 isTablet 为 true
context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true, isTablet: true } })
}
output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.mobile')
expect(output).not.toContain('.tablet')
expect(output).not.toContain('.desktop')
})

// 测试多个 if 块在一起的情况
it('多个 if 块处理: 不同 if 条件处理各自独立', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true, showHeader: false } })
}
const inputCSS = `
/*@mpx-if(isMobile)*/
.mobile { display: block; }
/*@mpx-end*/
/*@mpx-if(showHeader)*/
.header { height: 100px; }
/*@mpx-else*/
.header { height: 50px; }
/*@mpx-end*/
`
const output = cssIfLoader.call(context, inputCSS)
// 第一个 if 块:isMobile 为 true,输出 .mobile
expect(output).toContain('.mobile')
// 第二个 if 块:showHeader 为 false,应该保留 else 分支
expect(output).not.toContain('height: 100px;')
expect(output).toContain('height: 50px;')
})
it('多个 if 嵌套 elif 块处理', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true, showHeader: true } })
}
const inputCSS = `
/*@mpx-if(isMobile)*/
.mobile { display: block; }
/*@mpx-if(false)*/
.test1 {}
/*@mpx-elif(showHeader)*/
.test2 {}
/*@mpx-end*/
/*@mpx-end*/
`
const output = cssIfLoader.call(context, inputCSS)
expect(output).toContain('.mobile')
expect(output).toContain('.test2')
})

it('错误处理: 缺少开始标签', () => {
const context = {
cacheable: jest.fn(),
getMpx: () => ({ defs: { isMobile: true } })
}
const inputCSS = `
/*@mpx-elif(isMobile)*/
.mobile { display: block; }
`
// 预期这种情况下应该抛出异常或给出警告
expect(() => cssIfLoader.call(context, inputCSS)).toThrow(new Error('elif without a preceding if'))
})
})

0 comments on commit e44b162

Please sign in to comment.