@epa-wg/custom-element 0.0.33 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
package/README.md CHANGED
@@ -341,16 +341,16 @@ within template
341
341
  [css-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/scoped-css.html
342
342
  [slice-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/data-slices.html
343
343
  [hex-grid-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/hex-grid.html
344
- [hex-grid-image]: https://unpkg.com/@epa-wg/custom-element@0.0.33/demo/hex-grid-transform.png
344
+ [hex-grid-image]: https://unpkg.com/@epa-wg/custom-element@0.0.34/demo/hex-grid-transform.png
345
345
  [local-storage-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/local-storage.html
346
346
  [http-request-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/http-request.html
347
347
  [location-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/location.html
348
348
  [github-image]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg
349
349
  [npm-image]: https://img.shields.io/npm/v/@epa-wg/custom-element.svg
350
350
  [npm-url]: https://npmjs.org/package/@epa-wg/custom-element
351
- [coverage-image]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.33/coverage/src/custom-element/coverage.svg
352
- [coverage-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.33/coverage/src/custom-element/index.html
353
- [storybook-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.33/storybook-static/index.html?path=/story/welcome--introduction
351
+ [coverage-image]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.34/coverage/src/custom-element/coverage.svg
352
+ [coverage-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.34/coverage/src/custom-element/index.html
353
+ [storybook-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.34/storybook-static/index.html?path=/story/welcome--introduction
354
354
  [sandbox-url]: https://stackblitz.com/github/EPA-WG/custom-element?file=index.html
355
355
  [webcomponents-url]: https://www.webcomponents.org/element/@epa-wg/custom-element
356
356
  [webcomponents-img]: https://img.shields.io/badge/webcomponents.org-published-blue.svg
@@ -1,160 +1,160 @@
1
- /**
2
- * node xslDtd2Ide.cjs
3
- * would get xsl 1.0 schema and populate IntelliJ and VS Code IDE custom elements definitions
4
- *
5
- * This is one time use script as XSLT 1.0 schema is not changing.
6
- * DTD parsing here is not generic and cobers only particula XSLT 1.0 schema.
7
- */
8
- import { readFileSync, writeFileSync } from 'node:fs';
9
-
10
-
11
- const dtdText = await fetch( 'https://www.w3.org/1999/11/xslt10.dtd' )
12
- .then( ( response ) => response.text() )
13
- .then( ( body ) =>
14
- {
15
- return body;
16
- } );
17
- const matches = dtdText.match( /<([^>]*)>/g );
18
-
19
- const chopOff = ( s, begin = 1, end = 1 ) => s.substring( begin, s.length - end );
20
- const trim = s => s?.trim ? s.trim() : s;
21
-
22
- let lastComment = ''
23
- const dtdObj = { ENTITY: {}, ELEMENT: {} }
24
- for( const match of matches ){
25
- if( match.startsWith( '<!--' ) ) {
26
- lastComment = match;
27
- continue;
28
- }
29
- const body = chopOff( match, 2 );
30
- const arr = body.split( /\s/ );
31
- const name = arr[ 1 ].trim();
32
- const resolveRef = s => s ? ( s.startsWith ? ( s.startsWith( '%' ) ? dtdObj.ENTITY[ chopOff( s, 1, 0 ).replace( ';',
33
- '' ) ] : s ) : s ) : s;
34
- const attrObj = a =>
35
- {
36
- if( !a || Array.isArray( a ) || !a.trim )
37
- return a;
38
- const as = a.trim();
39
- if( 'CDATA,#PCDATA,NMTOKEN,NMTOKENS,'.includes( as + ',' ) )
40
- return as;
41
- const ar = as.split( ';' )
42
- const aa = ar[ 0 ].split( ' ' );
43
- // if( aa[0].includes('select')){debugger;}
44
- return { name: aa[ 0 ], type: resolveRef( aa[ 1 ] ), defValue: aa[ 1 ], required: (ar[1] || aa[ 2 ])?.trim() }
45
- };
46
- switch( arr[ 0 ] ){
47
- case 'ENTITY':{
48
- let key = arr[ 2 ];
49
- let val = body.substring( body.indexOf( key ) + key.length ).trim();
50
- let ss;
51
- if( val.startsWith( '"' ) || val.startsWith( "'" ) ) {
52
- val = chopOff( val );
53
- if( val.includes( '(#PCDATA' ) ) {
54
- val = val.replace( '(#PCDATA', '' ).replace( ')*', '' ).trim();
55
- ss = [ '#PCDATA', ...val.split( '\n' ).map( s => s.trim() ).map( resolveRef ).flat() ];
56
- } else
57
- ss = val.split( /[\n]/ ).map( s => s.replace( '|', '' ).trim() ).filter( s => s );
58
- } else
59
- ss = val.split( /[|\n]/ );
60
-
61
- const v = ss.map( trim ).filter( s => s ).map( resolveRef ).map( attrObj ).flat().filter( s => s );
62
- dtdObj.ENTITY[ key ] = !v.length ? '' : v.length === 1 ? v[ 0 ] : v;
63
- break;
64
- }
65
- case 'ELEMENT':
66
- dtdObj.ELEMENT[ name ] = { values: arr[ 2 ], attributes: [] };
67
- break;
68
- case 'ATTLIST':{
69
- const attrStr = body.split( name )[ 1 ].trim();
70
- const attrs = attrStr.split( '\n' ).map( s => s.trim() );
71
- const elementAttrs = dtdObj.ELEMENT[ name ].attributes;
72
- for( let a of attrs ){
73
- if( a.startsWith( '%' ) ) {
74
- const v = dtdObj.ENTITY[ chopOff( a.split( ';' )[ 0 ], 1, 0 ) ];
75
- if( !v ) {
76
- debugger;
77
- }
78
- Array.isArray( v )
79
- ? elementAttrs.push( ...v )
80
- : elementAttrs.push( v );
81
- } else
82
- elementAttrs.push( attrObj( a ) );
83
- }
84
-
85
- break;
86
- }
87
- }
88
- }
89
-
90
- // replace the tags list in custom-element.js
91
-
92
- const tagsCsv = Object.keys( dtdObj.ELEMENT ).map( s => s.replace( 'xsl:', '' ) ).join( ',' );
93
- const jsText = readFileSync( '../custom-element.js', 'utf8' )
94
- const updatedJs = jsText.replace( /^.*export const xslTags = .*$/mg,
95
- `export const xslTags = '${ tagsCsv }'.split(',');` );
96
- writeFileSync( '../custom-element.js', updatedJs );
97
-
98
- const vsCode = {
99
- "version": 1.1, tags: Object.keys( dtdObj.ELEMENT ).map( s => (
100
- { name : s.replace( 'xsl:', '' )
101
- , description : `${ s }`
102
- , attributes : dtdObj.ELEMENT[ s ].attributes.map( a => (
103
- { name : a.name
104
- , description: `${ JSON.stringify( a ) }`
105
- , type : "string"
106
- , required : a.required === '#REQUIRED'
107
- } ) )
108
- , references : [ { name: "MDN docs"
109
- , url : `https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/${s.replace( 'xsl:', '' )}`
110
- }]
111
- } ) )
112
- };
113
-
114
- writeFileSync( '.././ide/customData-xsl.json', JSON.stringify( vsCode, undefined, 4 ) );
115
-
116
- const intelliJ = {
117
- "$schema": "http://json.schemastore.org/web-types",
118
- "name": "@epa-wg/custom-element",
119
- "version": "0.0.33",
120
- "js-types-syntax": "typescript",
121
- "description-markup": "markdown",
122
- "contributions": {
123
- "html": {
124
- "elements": [
125
- ...Object.keys( dtdObj.ELEMENT ).map( s => (
126
- { name : s.replace( 'xsl:', '' )
127
- , description : `${ s }`
128
- , attributes : dtdObj.ELEMENT[ s ].attributes.map( a => (
129
- { name : a.name
130
- , description : `${ JSON.stringify( a ) }`
131
- , type : "string"
132
- , required : a.required === '#REQUIRED'
133
- } ) )
134
- , 'doc-url' : `https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/${s.replace( 'xsl:', '' )}`
135
- } ) ),
136
- {
137
- "name": "for-each",
138
- "description": "The <xsl:for-each> element selects a set of nodes and processes each of them in the same way. It is often used to iterate through a set of nodes or to change the current node. If one or more <xsl:sort> elements appear as the children of this element, sorting occurs before processing. Otherwise, nodes are processed in document order.",
139
- "doc-url": "https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each",
140
- "attributes": [
141
- {
142
- "name": "select",
143
- "description": "Uses an XPath expression to select nodes to be processed.",
144
- "required": true,
145
- "doc-url": "https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each#select",
146
- "value": {
147
- "type": "string"
148
- }
149
- }
150
- ]
151
- }
152
- ]
153
- }
154
- }
155
- };
156
-
157
-
158
- writeFileSync( '.././ide/web-types-xsl.json', JSON.stringify( intelliJ, undefined, 4 ) );
159
-
160
-
1
+ /**
2
+ * node xslDtd2Ide.cjs
3
+ * would get xsl 1.0 schema and populate IntelliJ and VS Code IDE custom elements definitions
4
+ *
5
+ * This is one time use script as XSLT 1.0 schema is not changing.
6
+ * DTD parsing here is not generic and cobers only particula XSLT 1.0 schema.
7
+ */
8
+ import { readFileSync, writeFileSync } from 'node:fs';
9
+
10
+
11
+ const dtdText = await fetch( 'https://www.w3.org/1999/11/xslt10.dtd' )
12
+ .then( ( response ) => response.text() )
13
+ .then( ( body ) =>
14
+ {
15
+ return body;
16
+ } );
17
+ const matches = dtdText.match( /<([^>]*)>/g );
18
+
19
+ const chopOff = ( s, begin = 1, end = 1 ) => s.substring( begin, s.length - end );
20
+ const trim = s => s?.trim ? s.trim() : s;
21
+
22
+ let lastComment = ''
23
+ const dtdObj = { ENTITY: {}, ELEMENT: {} }
24
+ for( const match of matches ){
25
+ if( match.startsWith( '<!--' ) ) {
26
+ lastComment = match;
27
+ continue;
28
+ }
29
+ const body = chopOff( match, 2 );
30
+ const arr = body.split( /\s/ );
31
+ const name = arr[ 1 ].trim();
32
+ const resolveRef = s => s ? ( s.startsWith ? ( s.startsWith( '%' ) ? dtdObj.ENTITY[ chopOff( s, 1, 0 ).replace( ';',
33
+ '' ) ] : s ) : s ) : s;
34
+ const attrObj = a =>
35
+ {
36
+ if( !a || Array.isArray( a ) || !a.trim )
37
+ return a;
38
+ const as = a.trim();
39
+ if( 'CDATA,#PCDATA,NMTOKEN,NMTOKENS,'.includes( as + ',' ) )
40
+ return as;
41
+ const ar = as.split( ';' )
42
+ const aa = ar[ 0 ].split( ' ' );
43
+ // if( aa[0].includes('select')){debugger;}
44
+ return { name: aa[ 0 ], type: resolveRef( aa[ 1 ] ), defValue: aa[ 1 ], required: (ar[1] || aa[ 2 ])?.trim() }
45
+ };
46
+ switch( arr[ 0 ] ){
47
+ case 'ENTITY':{
48
+ let key = arr[ 2 ];
49
+ let val = body.substring( body.indexOf( key ) + key.length ).trim();
50
+ let ss;
51
+ if( val.startsWith( '"' ) || val.startsWith( "'" ) ) {
52
+ val = chopOff( val );
53
+ if( val.includes( '(#PCDATA' ) ) {
54
+ val = val.replace( '(#PCDATA', '' ).replace( ')*', '' ).trim();
55
+ ss = [ '#PCDATA', ...val.split( '\n' ).map( s => s.trim() ).map( resolveRef ).flat() ];
56
+ } else
57
+ ss = val.split( /[\n]/ ).map( s => s.replace( '|', '' ).trim() ).filter( s => s );
58
+ } else
59
+ ss = val.split( /[|\n]/ );
60
+
61
+ const v = ss.map( trim ).filter( s => s ).map( resolveRef ).map( attrObj ).flat().filter( s => s );
62
+ dtdObj.ENTITY[ key ] = !v.length ? '' : v.length === 1 ? v[ 0 ] : v;
63
+ break;
64
+ }
65
+ case 'ELEMENT':
66
+ dtdObj.ELEMENT[ name ] = { values: arr[ 2 ], attributes: [] };
67
+ break;
68
+ case 'ATTLIST':{
69
+ const attrStr = body.split( name )[ 1 ].trim();
70
+ const attrs = attrStr.split( '\n' ).map( s => s.trim() );
71
+ const elementAttrs = dtdObj.ELEMENT[ name ].attributes;
72
+ for( let a of attrs ){
73
+ if( a.startsWith( '%' ) ) {
74
+ const v = dtdObj.ENTITY[ chopOff( a.split( ';' )[ 0 ], 1, 0 ) ];
75
+ if( !v ) {
76
+ debugger;
77
+ }
78
+ Array.isArray( v )
79
+ ? elementAttrs.push( ...v )
80
+ : elementAttrs.push( v );
81
+ } else
82
+ elementAttrs.push( attrObj( a ) );
83
+ }
84
+
85
+ break;
86
+ }
87
+ }
88
+ }
89
+
90
+ // replace the tags list in custom-element.js
91
+
92
+ const tagsCsv = Object.keys( dtdObj.ELEMENT ).map( s => s.replace( 'xsl:', '' ) ).join( ',' );
93
+ const jsText = readFileSync( '../custom-element.js', 'utf8' )
94
+ const updatedJs = jsText.replace( /^.*export const xslTags = .*$/mg,
95
+ `export const xslTags = '${ tagsCsv }'.split(',');` );
96
+ writeFileSync( '../custom-element.js', updatedJs );
97
+
98
+ const vsCode = {
99
+ "version": 1.1, tags: Object.keys( dtdObj.ELEMENT ).map( s => (
100
+ { name : s.replace( 'xsl:', '' )
101
+ , description : `${ s }`
102
+ , attributes : dtdObj.ELEMENT[ s ].attributes.map( a => (
103
+ { name : a.name
104
+ , description: `${ JSON.stringify( a ) }`
105
+ , type : "string"
106
+ , required : a.required === '#REQUIRED'
107
+ } ) )
108
+ , references : [ { name: "MDN docs"
109
+ , url : `https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/${s.replace( 'xsl:', '' )}`
110
+ }]
111
+ } ) )
112
+ };
113
+
114
+ writeFileSync( '.././ide/customData-xsl.json', JSON.stringify( vsCode, undefined, 4 ) );
115
+
116
+ const intelliJ = {
117
+ "$schema": "http://json.schemastore.org/web-types",
118
+ "name": "@epa-wg/custom-element",
119
+ "version": "0.0.34",
120
+ "js-types-syntax": "typescript",
121
+ "description-markup": "markdown",
122
+ "contributions": {
123
+ "html": {
124
+ "elements": [
125
+ ...Object.keys( dtdObj.ELEMENT ).map( s => (
126
+ { name : s.replace( 'xsl:', '' )
127
+ , description : `${ s }`
128
+ , attributes : dtdObj.ELEMENT[ s ].attributes.map( a => (
129
+ { name : a.name
130
+ , description : `${ JSON.stringify( a ) }`
131
+ , type : "string"
132
+ , required : a.required === '#REQUIRED'
133
+ } ) )
134
+ , 'doc-url' : `https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/${s.replace( 'xsl:', '' )}`
135
+ } ) ),
136
+ {
137
+ "name": "for-each",
138
+ "description": "The <xsl:for-each> element selects a set of nodes and processes each of them in the same way. It is often used to iterate through a set of nodes or to change the current node. If one or more <xsl:sort> elements appear as the children of this element, sorting occurs before processing. Otherwise, nodes are processed in document order.",
139
+ "doc-url": "https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each",
140
+ "attributes": [
141
+ {
142
+ "name": "select",
143
+ "description": "Uses an XPath expression to select nodes to be processed.",
144
+ "required": true,
145
+ "doc-url": "https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each#select",
146
+ "value": {
147
+ "type": "string"
148
+ }
149
+ }
150
+ ]
151
+ }
152
+ ]
153
+ }
154
+ }
155
+ };
156
+
157
+
158
+ writeFileSync( '.././ide/web-types-xsl.json', JSON.stringify( intelliJ, undefined, 4 ) );
159
+
160
+
package/custom-element.js CHANGED
@@ -89,7 +89,12 @@ obj2node( o, tag, doc )
89
89
  return create(tag,o,doc);
90
90
  if( t === 'number' )
91
91
  return create(tag,''+o,doc);
92
-
92
+ if( isNode(o) )
93
+ {
94
+ const el = create(tag);
95
+ el.append(o);
96
+ return el;
97
+ }
93
98
  if( o instanceof Array )
94
99
  { const ret = create('array','',doc);
95
100
  o.map( ae => ret.append( obj2node(ae,tag,doc)) );
@@ -103,13 +108,20 @@ obj2node( o, tag, doc )
103
108
  }
104
109
  const ret = create(tag,'',doc);
105
110
  for( let k in o )
106
- if( isNode(o[k]) || typeof o[k] ==='function' || o[k] instanceof Window )
111
+ {
112
+ if( typeof o[ k ] === 'function' || o[ k ] instanceof Window )
107
113
  continue
108
- else
109
- if( typeof o[k] !== "object" && isValidTagName(k) )
110
- ret.setAttribute(k, o[k] );
114
+ if( isNode( o[ k ] ) )
115
+ { if( k === 'data' || k==='value' )
116
+ ;
111
117
  else
112
- ret.append(obj2node(o[k], k, doc))
118
+ continue
119
+ }
120
+ if( typeof o[ k ] !== "object" && isValidTagName( k ) )
121
+ ret.setAttribute( k, o[ k ] )
122
+ else
123
+ ret.append( obj2node( o[ k ], k, doc ) )
124
+ }
113
125
  return ret;
114
126
  }
115
127
  export function
@@ -234,6 +246,8 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
234
246
  const d = xml2dom( '<xhtml/>' )
235
247
  , n = d.importNode(r, true);
236
248
  d.replaceChild(n,d.documentElement);
249
+ if( n.namespaceURI === HTML_NS_URL && !attr(n,'xmlns'))
250
+ n.setAttribute('xmlns',HTML_NS_URL);
237
251
  return xslHtmlNs(n);
238
252
  };
239
253
  if( e )
@@ -311,7 +325,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
311
325
  , name = attr(a,'name');
312
326
 
313
327
  declaredAttributes.push(name);
314
- if( a.childNodes.length)
328
+ if( a.childNodes.length )
315
329
  hardcodedAttributes[name] = a.textContent;
316
330
 
317
331
  payload.append(p);
@@ -536,6 +550,8 @@ export function mergeAttr( from, to )
536
550
  { for( let a of from.attributes)
537
551
  try
538
552
  { const name = a.name;
553
+ if( name.startsWith('xmlns') )
554
+ continue;
539
555
  if( a.namespaceURI )
540
556
  { if( !to.hasAttributeNS(a.namespaceURI, name) || to.getAttributeNS(a.namespaceURI, name) !== a.value )
541
557
  to.setAttributeNS( a.namespaceURI, name, a.value )
@@ -665,14 +681,19 @@ export const toXsl = (el, defParent) => {
665
681
  x.setAttribute( a.name, a.value );
666
682
  while(el.firstChild)
667
683
  x.append(el.firstChild);
684
+ const replacement = el.localName === 'if' || el.localName === 'choose' ? (() => {
685
+ const span = create('span');
686
+ span.append(x);
687
+ return span;
688
+ })() : x;
668
689
  if( el.parentElement )
669
- el.parentElement.replaceChild( x, el );
690
+ el.parentElement.replaceChild( replacement, el );
670
691
  else
671
692
  { const p = (el.parentElement || defParent)
672
693
  , arr = [...p.childNodes];
673
694
  arr.forEach((n, i) => {
674
695
  if (n === el)
675
- arr[i] = x;
696
+ arr[i] = replacement;
676
697
  });
677
698
  p.replaceChildren(...arr);
678
699
  }
@@ -685,7 +706,7 @@ CustomElement extends HTMLElement
685
706
  async connectedCallback()
686
707
  {
687
708
  if(this.firstElementChild && this.firstElementChild.localName !== 'template')
688
- console.warn('custom-element used without template wrapping content\n', this.outerHTML);
709
+ console.log('custom-element used without template wrapping content\n', this.outerHTML);
689
710
  const templateRoots = await loadTemplateRoots( attr( this, 'src' ), this )
690
711
  , tag = attr( this, 'tag' )
691
712
  , tagName = tag ? tag : 'dce-'+crypto.randomUUID();
package/demo/a.html CHANGED
@@ -63,11 +63,18 @@
63
63
  <body>
64
64
 
65
65
 
66
+ <custom-element tag="dce-link2" >
67
+ <template>
66
68
 
67
- <custom-element src="./html-template.html#dwc-logo">
68
- <template><i>loading SVG from templates file ...</i></template>
69
- </custom-element>
69
+ <attribute name="p3" select="//attributes/p3 ?? 'def_P3' "></attribute>
70
70
 
71
+ p3: <code data-testid="t3">{$p3}</code>
72
+ </template>
73
+ </custom-element>
74
+ <section>
75
+ <dce-link2 id="dce2" p1="123" p2="override ignored as select is defined"></dce-link2>
76
+
77
+ </section>
71
78
 
72
79
  </body>
73
80
  </html>
@@ -1,6 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
3
3
  <head>
4
+ <meta charset="utf-8">
4
5
  <title>template based on HTML file</title>
5
6
  <style>svg {
6
7
  width: 4rem;
@@ -8,9 +9,9 @@
8
9
  </head>
9
10
  <body>
10
11
  <script>console.error('Stranger danger!')</script>
11
- <b id="wave">👋</b>
12
- <b id="ok">👌</b>
13
- <svg id="dwc-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216 209.18">
12
+ <b id="wave" data-testid="wave" >👋</b>
13
+ <b id="ok" data-testid="ok" >👌</b>
14
+ <svg id="dwc-logo" data-testid="dwc-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216 209.18">
14
15
  <defs>
15
16
  <style>.cls-1 {
16
17
  fill: #c2e6f1;
@@ -73,7 +74,7 @@
73
74
  <path class="cls-1"
74
75
  d="m184.02,96.93l20.64-11.92c.47-.27.47-.79,0-1.06l-20.65-11.92h0c-4.44-2.57-8.22-2.57-12.67,0l-20.65,11.92c-.47.27-.47.79,0,1.06l20.64,11.92c4.44,2.57,8.22,2.57,12.67,0h0Z"/>
75
76
  </svg>
76
- <math id="sophomores-dream" display="block">
77
+ <math id="sophomores-dream" data-testid="ml-test" display="block">
77
78
  <mrow>
78
79
  <msubsup>
79
80
  <mo>∫</mo>