schema.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Schema from '../../src/model/schema';
  6. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  7. import Model from '../../src/model/model';
  8. import Element from '../../src/model/element';
  9. import Position from '../../src/model/position';
  10. import Text from '../../src/model/text';
  11. import { setData } from '../../src/dev-utils/model';
  12. describe( 'Schema', () => {
  13. let schema, root1, r1p1, r1p2, r1bQ, r1bQp, root2;
  14. beforeEach( () => {
  15. schema = new Schema();
  16. root1 = new Element( '$root', null, [
  17. new Element( 'paragraph', null, 'foo' ),
  18. new Element( 'paragraph', { align: 'right' }, 'bar' ),
  19. new Element( 'blockQuote', null, [
  20. new Element( 'paragraph', null, 'foo' )
  21. ] )
  22. ] );
  23. r1p1 = root1.getChild( 0 );
  24. r1p2 = root1.getChild( 1 );
  25. r1bQ = root1.getChild( 2 );
  26. r1bQp = r1bQ.getChild( 0 );
  27. root2 = new Element( '$root2' );
  28. } );
  29. describe( 'register()', () => {
  30. it( 'allows registering an item', () => {
  31. schema.register( 'foo' );
  32. expect( schema.getRule( 'foo' ) ).to.be.an( 'object' );
  33. } );
  34. it( 'copies rules', () => {
  35. const rules = {};
  36. schema.register( 'foo', rules );
  37. rules.isBlock = true;
  38. expect( schema.getRules().foo ).to.not.have.property( 'isBlock' );
  39. } );
  40. it( 'throws when trying to register for a single item twice', () => {
  41. schema.register( 'foo' );
  42. expect( () => {
  43. schema.register( 'foo' );
  44. } ).to.throw( CKEditorError, /^schema-cannot-register-item-twice:/ );
  45. } );
  46. } );
  47. describe( 'extend()', () => {
  48. it( 'allows extending item\'s rules', () => {
  49. schema.register( 'foo' );
  50. schema.extend( 'foo', {
  51. isBlock: true
  52. } );
  53. expect( schema.getRule( 'foo' ) ).to.have.property( 'isBlock', true );
  54. } );
  55. it( 'copies rules', () => {
  56. schema.register( 'foo', {} );
  57. const rules = {};
  58. schema.extend( 'foo', rules );
  59. rules.isBlock = true;
  60. expect( schema.getRules().foo ).to.not.have.property( 'isBlock' );
  61. } );
  62. it( 'throws when trying to extend a not yet registered item', () => {
  63. expect( () => {
  64. schema.extend( 'foo' );
  65. } ).to.throw( CKEditorError, /^schema-cannot-extend-missing-item:/ );
  66. } );
  67. } );
  68. describe( 'getRules()', () => {
  69. it( 'returns compiled rules', () => {
  70. schema.register( '$root' );
  71. schema.register( 'foo', {
  72. allowIn: '$root'
  73. } );
  74. schema.extend( 'foo', {
  75. isBlock: true
  76. } );
  77. const rules = schema.getRules();
  78. expect( rules.foo ).to.deep.equal( {
  79. name: 'foo',
  80. allowIn: [ '$root' ],
  81. allowAttributes: [],
  82. isBlock: true
  83. } );
  84. } );
  85. it( 'copies all is* types', () => {
  86. schema.register( 'foo', {
  87. isBlock: true,
  88. isFoo: true
  89. } );
  90. schema.extend( 'foo', {
  91. isBar: true,
  92. isFoo: false // Just to check that the last one wins.
  93. } );
  94. const rules = schema.getRules();
  95. expect( rules.foo ).to.have.property( 'isBlock', true );
  96. expect( rules.foo ).to.have.property( 'isFoo', false );
  97. expect( rules.foo ).to.have.property( 'isBar', true );
  98. } );
  99. it( 'does not recompile rules if not needed', () => {
  100. schema.register( 'foo' );
  101. expect( schema.getRules() ).to.equal( schema.getRules() );
  102. } );
  103. it( 'ensures no duplicates in allowIn', () => {
  104. schema.register( '$root' );
  105. schema.register( 'foo', {
  106. allowIn: '$root'
  107. } );
  108. schema.extend( 'foo', {
  109. allowIn: '$root'
  110. } );
  111. const rules = schema.getRules();
  112. expect( rules.foo ).to.deep.equal( {
  113. name: 'foo',
  114. allowIn: [ '$root' ],
  115. allowAttributes: []
  116. } );
  117. } );
  118. it( 'ensures no unregistered items in allowIn', () => {
  119. schema.register( 'foo', {
  120. allowIn: '$root'
  121. } );
  122. const rules = schema.getRules();
  123. expect( rules.foo ).to.deep.equal( {
  124. name: 'foo',
  125. allowIn: [],
  126. allowAttributes: []
  127. } );
  128. } );
  129. it( 'ensures no duplicates in allowAttributes', () => {
  130. schema.register( 'paragraph', {
  131. allowAttributes: 'foo'
  132. } );
  133. schema.extend( 'paragraph', {
  134. allowAttributes: 'foo'
  135. } );
  136. const rules = schema.getRules();
  137. expect( rules.paragraph ).to.deep.equal( {
  138. name: 'paragraph',
  139. allowIn: [],
  140. allowAttributes: [ 'foo' ]
  141. } );
  142. } );
  143. it( 'ensures no duplicates in allowAttributes duplicated by allowAttributesOf', () => {
  144. schema.register( 'paragraph', {
  145. allowAttributes: 'foo',
  146. allowAttributesOf: '$block'
  147. } );
  148. schema.register( '$block', {
  149. allowAttributes: 'foo'
  150. } );
  151. const rules = schema.getRules();
  152. expect( rules.paragraph ).to.deep.equal( {
  153. name: 'paragraph',
  154. allowIn: [],
  155. allowAttributes: [ 'foo' ]
  156. } );
  157. } );
  158. } );
  159. describe( 'getRule()', () => {
  160. // TODO
  161. } );
  162. describe( 'isRegistered()', () => {
  163. it( 'returns true if an item was registered', () => {
  164. schema.register( 'foo' );
  165. expect( schema.isRegistered( 'foo' ) ).to.be.true;
  166. } );
  167. it( 'returns false if an item was not registered', () => {
  168. expect( schema.isRegistered( 'foo' ) ).to.be.false;
  169. } );
  170. } );
  171. describe( 'isBlock()', () => {
  172. it( 'returns true if an item was registered as a block', () => {
  173. schema.register( 'foo', {
  174. isBlock: true
  175. } );
  176. expect( schema.isBlock( 'foo' ) ).to.be.true;
  177. } );
  178. it( 'returns false if an item was not registered as a block', () => {
  179. schema.register( 'foo' );
  180. expect( schema.isBlock( 'foo' ) ).to.be.false;
  181. } );
  182. it( 'returns false if an item was not registered at all', () => {
  183. expect( schema.isBlock( 'foo' ) ).to.be.false;
  184. } );
  185. } );
  186. describe( 'isLimit()', () => {
  187. it( 'returns true if an item was registered as a limit element', () => {
  188. schema.register( 'foo', {
  189. isLimit: true
  190. } );
  191. expect( schema.isLimit( 'foo' ) ).to.be.true;
  192. } );
  193. it( 'returns false if an item was not registered as a limit element', () => {
  194. schema.register( 'foo' );
  195. expect( schema.isLimit( 'foo' ) ).to.be.false;
  196. } );
  197. it( 'returns false if an item was not registered at all', () => {
  198. expect( schema.isLimit( 'foo' ) ).to.be.false;
  199. } );
  200. } );
  201. describe( 'isObject()', () => {
  202. it( 'returns true if an item was registered as an object', () => {
  203. schema.register( 'foo', {
  204. isObject: true
  205. } );
  206. expect( schema.isObject( 'foo' ) ).to.be.true;
  207. } );
  208. it( 'returns false if an item was not registered as an object', () => {
  209. schema.register( 'foo' );
  210. expect( schema.isObject( 'foo' ) ).to.be.false;
  211. } );
  212. it( 'returns false if an item was not registered at all', () => {
  213. expect( schema.isObject( 'foo' ) ).to.be.false;
  214. } );
  215. } );
  216. describe( 'checkChild()', () => {
  217. beforeEach( () => {
  218. schema.register( '$root' );
  219. schema.register( 'paragraph', {
  220. allowIn: '$root'
  221. } );
  222. schema.register( '$text', {
  223. allowIn: 'paragraph'
  224. } );
  225. } );
  226. it( 'accepts an element as a context and a node name as a child', () => {
  227. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.true;
  228. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  229. } );
  230. it( 'accepts a position as a context', () => {
  231. const posInRoot = Position.createAt( root1 );
  232. const posInParagraph = Position.createAt( r1p1 );
  233. expect( schema.checkChild( posInRoot, 'paragraph' ) ).to.be.true;
  234. expect( schema.checkChild( posInRoot, '$text' ) ).to.be.false;
  235. expect( schema.checkChild( posInParagraph, '$text' ) ).to.be.true;
  236. expect( schema.checkChild( posInParagraph, 'paragraph' ) ).to.be.false;
  237. } );
  238. // This is a temporary feature which is needed to make the current V->M conversion works.
  239. // It should be removed once V->M conversion uses real positions.
  240. // Of course, real positions have this advantage that we know element attributes at this point.
  241. it( 'accepts an array of element names as a context', () => {
  242. const contextInRoot = [ '$root' ];
  243. const contextInParagraph = [ '$root', 'paragraph' ];
  244. expect( schema.checkChild( contextInRoot, 'paragraph' ) ).to.be.true;
  245. expect( schema.checkChild( contextInRoot, '$text' ) ).to.be.false;
  246. expect( schema.checkChild( contextInParagraph, '$text' ) ).to.be.true;
  247. expect( schema.checkChild( contextInParagraph, 'paragraph' ) ).to.be.false;
  248. } );
  249. it( 'accepts an array of elements as a context', () => {
  250. const contextInRoot = [ root1 ];
  251. const contextInParagraph = [ root1, r1p1 ];
  252. expect( schema.checkChild( contextInRoot, 'paragraph' ) ).to.be.true;
  253. expect( schema.checkChild( contextInRoot, '$text' ) ).to.be.false;
  254. expect( schema.checkChild( contextInParagraph, '$text' ) ).to.be.true;
  255. expect( schema.checkChild( contextInParagraph, 'paragraph' ) ).to.be.false;
  256. } );
  257. // Again, this is needed temporarily to handle current V->M conversion
  258. it( 'accepts a mixed array of elements and strings as a context', () => {
  259. const contextInParagraph = [ '$root', r1p1 ];
  260. expect( schema.checkChild( contextInParagraph, '$text' ) ).to.be.true;
  261. expect( schema.checkChild( contextInParagraph, 'paragraph' ) ).to.be.false;
  262. } );
  263. it( 'accepts a node as a child', () => {
  264. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  265. expect( schema.checkChild( root1, new Text( 'foo' ) ) ).to.be.false;
  266. } );
  267. // TODO checks fires event
  268. // TODO checks with a custom context array
  269. } );
  270. describe( 'checkAttribute()', () => {
  271. beforeEach( () => {
  272. schema.register( 'paragraph', {
  273. allowAttributes: 'align'
  274. } );
  275. schema.register( '$text', {
  276. allowAttributes: 'bold'
  277. } );
  278. } );
  279. it( 'accepts an element as a context', () => {
  280. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  281. expect( schema.checkAttribute( r1p1, 'bold' ) ).to.be.false;
  282. } );
  283. it( 'accepts a text as a context', () => {
  284. expect( schema.checkAttribute( new Text( 'foo' ), 'bold' ) ).to.be.true;
  285. expect( schema.checkAttribute( new Text( 'foo' ), 'align' ) ).to.be.false;
  286. } );
  287. // TODO checks fires event
  288. // TODO checks with a custom context array
  289. } );
  290. describe( 'getLimitElement()', () => {
  291. let model, doc, root;
  292. beforeEach( () => {
  293. model = new Model();
  294. doc = model.document;
  295. schema = model.schema;
  296. root = doc.createRoot();
  297. schema.register( 'div', {
  298. inheritAllFrom: '$block'
  299. } );
  300. schema.register( 'article', {
  301. inheritAllFrom: '$block',
  302. allowIn: 'section'
  303. } );
  304. schema.register( 'section', {
  305. inheritAllFrom: '$block',
  306. allowIn: 'div'
  307. } );
  308. schema.register( 'paragraph', {
  309. inheritAllFrom: '$block',
  310. allowIn: 'article'
  311. } );
  312. schema.register( 'widget', {
  313. inheritAllFrom: '$block',
  314. allowIn: 'div'
  315. } );
  316. schema.register( 'image', {
  317. inheritAllFrom: '$block',
  318. allowIn: 'widget'
  319. } );
  320. schema.register( 'caption', {
  321. inheritAllFrom: '$block',
  322. allowIn: 'image'
  323. } );
  324. } );
  325. it( 'always returns $root element if any other limit was not defined', () => {
  326. setData( model, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
  327. expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
  328. } );
  329. it( 'returns the limit element which is the closest element to common ancestor for collapsed selection', () => {
  330. schema.extend( 'article', { isLimit: true } );
  331. schema.extend( 'section', { isLimit: true } );
  332. setData( model, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
  333. const article = root.getNodeByPath( [ 0, 0, 0 ] );
  334. expect( schema.getLimitElement( doc.selection ) ).to.equal( article );
  335. } );
  336. it( 'returns the limit element which is the closest element to common ancestor for non-collapsed selection', () => {
  337. schema.extend( 'article', { isLimit: true } );
  338. schema.extend( 'section', { isLimit: true } );
  339. setData( model, '<div><section><article>[foo</article><article>bar]</article></section></div>' );
  340. const section = root.getNodeByPath( [ 0, 0 ] );
  341. expect( schema.getLimitElement( doc.selection ) ).to.equal( section );
  342. } );
  343. it( 'works fine with multi-range selections', () => {
  344. schema.extend( 'article', { isLimit: true } );
  345. schema.extend( 'widget', { isLimit: true } );
  346. schema.extend( 'div', { isLimit: true } );
  347. setData(
  348. model,
  349. '<div>' +
  350. '<section>' +
  351. '<article>' +
  352. '<paragraph>[foo]</paragraph>' +
  353. '</article>' +
  354. '</section>' +
  355. '<widget>' +
  356. '<image>' +
  357. '<caption>b[a]r</caption>' +
  358. '</image>' +
  359. '</widget>' +
  360. '</div>'
  361. );
  362. const div = root.getNodeByPath( [ 0 ] );
  363. expect( schema.getLimitElement( doc.selection ) ).to.equal( div );
  364. } );
  365. it( 'works fine with multi-range selections even if limit elements are not defined', () => {
  366. setData(
  367. model,
  368. '<div>' +
  369. '<section>' +
  370. '<article>' +
  371. '<paragraph>[foo]</paragraph>' +
  372. '</article>' +
  373. '</section>' +
  374. '</div>' +
  375. '<section>b[]ar</section>'
  376. );
  377. expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
  378. } );
  379. } );
  380. describe( 'rules compilation', () => {
  381. describe( 'allowIn cases', () => {
  382. it( 'passes $root>paragraph', () => {
  383. schema.register( '$root' );
  384. schema.register( 'paragraph', {
  385. allowIn: '$root'
  386. } );
  387. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  388. } );
  389. it( 'passes $root>paragraph and $root2>paragraph – support for array values', () => {
  390. schema.register( '$root' );
  391. schema.register( '$root2' );
  392. schema.register( 'paragraph', {
  393. allowIn: [ '$root', '$root2' ]
  394. } );
  395. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  396. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  397. } );
  398. it( 'passes $root>paragraph[align] – attributes does not matter', () => {
  399. schema.register( '$root' );
  400. schema.register( 'paragraph', {
  401. allowIn: '$root'
  402. } );
  403. expect( schema.checkChild( root1, r1p2 ) ).to.be.true;
  404. } );
  405. it( 'passes $root>div>div – in case of circular refs', () => {
  406. schema.register( '$root' );
  407. schema.register( 'div', {
  408. allowIn: [ '$root', 'div' ]
  409. } );
  410. const div = new Element( 'div' );
  411. root1.appendChildren( div );
  412. const div2 = new Element( 'div' );
  413. expect( schema.checkChild( div, div2 ) ).to.be.true;
  414. } );
  415. it( 'passes $root>div>div – in case of circular refs, when div1==div2', () => {
  416. schema.register( '$root' );
  417. schema.register( 'div', {
  418. allowIn: [ '$root', 'div' ]
  419. } );
  420. const div = new Element( 'div' );
  421. root1.appendChildren( div );
  422. expect( schema.checkChild( div, div ) ).to.be.true;
  423. } );
  424. it( 'rejects $root>paragraph – unregistered paragraph', () => {
  425. schema.register( '$root' );
  426. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  427. } );
  428. it( 'rejects $root>paragraph – registered different item', () => {
  429. schema.register( '$root' );
  430. schema.register( 'paragraph' );
  431. schema.register( 'listItem', {
  432. allowIn: '$root'
  433. } );
  434. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  435. } );
  436. it( 'rejects $root>paragraph – paragraph allowed in different context', () => {
  437. schema.register( '$root' );
  438. schema.register( '$fancyRoot' );
  439. schema.register( 'paragraph', {
  440. allowIn: '$fancyRoot'
  441. } );
  442. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  443. } );
  444. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root', () => {
  445. schema.register( '$root' );
  446. schema.register( 'paragraph', {
  447. allowIn: '$root'
  448. } );
  449. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  450. } );
  451. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root v2', () => {
  452. schema.register( '$root' );
  453. schema.register( 'blockQuote', {
  454. allowIn: '$root'
  455. } );
  456. schema.register( 'paragraph', {
  457. allowIn: '$root'
  458. } );
  459. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  460. } );
  461. it( 'rejects $root>blockQuote>paragraph>$text - since paragraph is not allowed in blockQuote', () => {
  462. schema.register( '$root' );
  463. schema.register( 'paragraph', {
  464. allowIn: '$root'
  465. } );
  466. schema.register( '$text', {
  467. allowIn: 'paragraph'
  468. } );
  469. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  470. } );
  471. it( 'rejects $root>blockQuote>paragraph>$text - since blockQuote is not allowed in $root', () => {
  472. schema.register( '$root' );
  473. schema.register( 'blockQuote' );
  474. schema.register( 'paragraph', {
  475. allowIn: [ 'blockQuote', '$root' ]
  476. } );
  477. schema.register( '$text', {
  478. allowIn: 'paragraph'
  479. } );
  480. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  481. } );
  482. } );
  483. describe( 'allowWhere cases', () => {
  484. it( 'passes $root>paragraph – paragraph inherits from $block', () => {
  485. schema.register( '$root' );
  486. schema.register( '$block', {
  487. allowIn: '$root'
  488. } );
  489. schema.register( 'paragraph', {
  490. allowWhere: '$block'
  491. } );
  492. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  493. } );
  494. it( 'supports the array syntax', () => {
  495. schema.register( '$root' );
  496. schema.register( '$root2' );
  497. schema.register( '$block', {
  498. allowIn: '$root'
  499. } );
  500. schema.register( '$block2', {
  501. allowIn: '$root2'
  502. } );
  503. schema.register( 'paragraph', {
  504. allowWhere: [ '$block', '$block2' ]
  505. } );
  506. expect( schema.checkChild( root1, r1p1 ), '$root' ).to.be.true;
  507. expect( schema.checkChild( root2, r1p1 ), '$root2' ).to.be.true;
  508. } );
  509. // This checks if some inapropriate caching or preprocessing isn't applied by register().
  510. it( 'passes $root>paragraph – paragraph inherits from $block, order of rules does not matter', () => {
  511. schema.register( '$root' );
  512. schema.register( 'paragraph', {
  513. allowWhere: '$block'
  514. } );
  515. schema.register( '$block', {
  516. allowIn: '$root'
  517. } );
  518. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  519. } );
  520. it( 'passes $root>paragraph – paragraph inherits from $specialBlock which inherits from $block', () => {
  521. schema.register( '$root' );
  522. schema.register( '$block', {
  523. allowIn: '$root'
  524. } );
  525. schema.register( '$specialBlock', {
  526. allowWhere: '$block'
  527. } );
  528. schema.register( 'paragraph', {
  529. allowWhere: '$specialBlock'
  530. } );
  531. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  532. } );
  533. it( 'rejects $root>paragraph – paragraph inherits from $block but $block is not allowed in $root', () => {
  534. schema.register( '$root' );
  535. schema.register( '$block' );
  536. schema.register( 'paragraph', {
  537. allowWhere: '$block'
  538. } );
  539. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  540. } );
  541. it( 'rejects $root>paragraph>$text – paragraph inherits from $block but $block is not allowed in $root', () => {
  542. schema.register( '$root' );
  543. schema.register( '$block' );
  544. schema.register( 'paragraph', {
  545. allowWhere: '$block'
  546. } );
  547. schema.register( '$text', {
  548. allowIn: 'paragraph'
  549. } );
  550. expect( schema.checkChild( root1, r1p1.getChild( 0 ) ) ).to.be.false;
  551. } );
  552. } );
  553. describe( 'allowContentOf cases', () => {
  554. it( 'passes $root2>paragraph – $root2 inherits from $root', () => {
  555. schema.register( '$root' );
  556. schema.register( 'paragraph', {
  557. allowIn: '$root'
  558. } );
  559. schema.register( '$root2', {
  560. allowContentOf: '$root'
  561. } );
  562. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  563. } );
  564. it( 'supports the array syntax', () => {
  565. schema.register( '$root' );
  566. schema.register( '$root2' );
  567. schema.register( 'paragraph', {
  568. allowIn: '$root'
  569. } );
  570. schema.register( 'heading1', {
  571. allowIn: '$root2'
  572. } );
  573. schema.register( '$root3', {
  574. allowContentOf: [ '$root', '$root2' ]
  575. } );
  576. const root3 = new Element( '$root3' );
  577. const heading1 = new Element( 'heading1' );
  578. expect( schema.checkChild( root3, r1p1 ), 'paragraph' ).to.be.true;
  579. expect( schema.checkChild( root3, heading1 ), 'heading1' ).to.be.true;
  580. } );
  581. it( 'passes $root2>paragraph – $root2 inherits from $root, order of rules does not matter', () => {
  582. schema.register( '$root' );
  583. schema.register( '$root2', {
  584. allowContentOf: '$root'
  585. } );
  586. schema.register( 'paragraph', {
  587. allowIn: '$root'
  588. } );
  589. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  590. } );
  591. it( 'passes $root>paragraph>$text – paragraph inherits content of $block', () => {
  592. schema.register( '$root' );
  593. schema.register( '$block' );
  594. schema.register( 'paragraph', {
  595. allowIn: '$root',
  596. allowContentOf: '$block'
  597. } );
  598. schema.register( '$text', {
  599. allowIn: '$block'
  600. } );
  601. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  602. } );
  603. // TODO we need to make it possible to disallow bQ in bQ.
  604. it( 'passes $root>blockQuote>paragraph – blockQuote inherits content of $root', () => {
  605. schema.register( '$root' );
  606. schema.register( 'blockQuote', {
  607. allowIn: '$root',
  608. allowContentOf: '$root'
  609. } );
  610. schema.register( 'paragraph', {
  611. allowIn: '$root'
  612. } );
  613. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.true;
  614. } );
  615. it( 'rejects $root2>paragraph – $root2 inherits from $root, but paragraph is not allowed there anyway', () => {
  616. schema.register( '$root' );
  617. schema.register( 'paragraph' );
  618. schema.register( '$root2', {
  619. allowContentOf: '$root'
  620. } );
  621. expect( schema.checkChild( root2, r1p1 ) ).to.be.false;
  622. } );
  623. } );
  624. describe( 'mix of allowContentOf and allowWhere', () => {
  625. it( 'passes $root>paragraph>$text – paragraph inherits all from $block', () => {
  626. schema.register( '$root' );
  627. schema.register( '$block', {
  628. allowIn: '$root'
  629. } );
  630. schema.register( 'paragraph', {
  631. allowContentOf: '$block',
  632. allowWhere: '$block'
  633. } );
  634. schema.register( '$text', {
  635. allowIn: '$block'
  636. } );
  637. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  638. } );
  639. it( 'passes $root>paragraph and $root2>paragraph – where $root2 inherits content of $root' +
  640. 'and paragraph inherits allowWhere from $block', () => {
  641. schema.register( '$root' );
  642. schema.register( '$root2', {
  643. allowContentOf: '$root'
  644. } );
  645. schema.register( '$block', {
  646. allowIn: '$root'
  647. } );
  648. schema.register( 'paragraph', {
  649. allowWhere: '$block'
  650. } );
  651. expect( schema.checkChild( root1, 'paragraph' ), 'root1' ).to.be.true;
  652. expect( schema.checkChild( root2, 'paragraph' ), 'root2' ).to.be.true;
  653. } );
  654. it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  655. schema.register( 'b' );
  656. schema.register( 'a', { allowIn: 'b' } );
  657. schema.register( 'c', { allowContentOf: 'b' } );
  658. schema.register( 'd', { allowContentOf: 'c' } );
  659. const d = new Element( 'd' );
  660. expect( schema.checkChild( d, 'a' ) ).to.be.true;
  661. } );
  662. // This case won't pass becuase we compile the rules in a pretty naive way.
  663. // To make chains like this work we'd need to repeat compilation of allowContentOf rules
  664. // as long as the previous iteration found something to compile.
  665. // This way, even though we'd not compile d<-c in the first run, we'd still find b<-c
  666. // and since we've found something, we'd now try d<-c which would work.
  667. //
  668. // We ignore those situations for now as they are very unlikely to happen and would
  669. // significantly raised the complexity of rule compilation.
  670. //
  671. // it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  672. // schema.register( 'b' );
  673. // schema.register( 'a', { allowIn: 'b' } );
  674. // schema.register( 'd', { allowContentOf: 'c' } );
  675. // schema.register( 'c', { allowContentOf: 'b' } );
  676. //
  677. // const d = new Element( 'd' );
  678. //
  679. // expect( schema.checkChild( d, 'a' ) ).to.be.true;
  680. // } );
  681. } );
  682. describe( 'inheritAllFrom', () => {
  683. it( 'passes $root>paragraph – paragraph inherits allowIn from $block', () => {
  684. schema.register( '$root' );
  685. schema.register( '$block', {
  686. allowIn: '$root'
  687. } );
  688. schema.register( 'paragraph', {
  689. inheritAllFrom: '$block'
  690. } );
  691. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  692. } );
  693. it( 'passes $root>paragraph>$text – paragraph inherits allowed content of $block', () => {
  694. schema.register( '$root' );
  695. schema.register( '$block', {
  696. allowIn: '$root'
  697. } );
  698. schema.register( '$text', {
  699. allowIn: '$block'
  700. } );
  701. schema.register( 'paragraph', {
  702. inheritAllFrom: '$block'
  703. } );
  704. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  705. } );
  706. it( 'passes $root>paragraph>$text – paragraph inherits allowIn from $block through $block\'s allowWhere', () => {
  707. schema.register( '$root' );
  708. schema.register( '$blockProto', {
  709. allowIn: '$root'
  710. } );
  711. schema.register( '$block', {
  712. allowWhere: '$blockProto'
  713. } );
  714. schema.register( 'paragraph', {
  715. inheritAllFrom: '$block'
  716. } );
  717. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  718. } );
  719. it( 'passes $root>paragraph>$text – paragraph inherits allowIn from $block through $block\'s allowWhere', () => {
  720. schema.register( '$root' );
  721. schema.register( '$blockProto' );
  722. schema.register( '$block', {
  723. allowContentOf: '$blockProto',
  724. allowIn: '$root'
  725. } );
  726. schema.register( '$text', {
  727. allowIn: '$blockProto'
  728. } );
  729. schema.register( 'paragraph', {
  730. inheritAllFrom: '$block'
  731. } );
  732. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  733. } );
  734. } );
  735. // We need to handle cases where some independent features registered rules which might use
  736. // optional elements (elements which might not have been registered).
  737. describe( 'missing structure rules', () => {
  738. it( 'does not break when trying to check a child which is not registered', () => {
  739. schema.register( '$root' );
  740. expect( schema.checkChild( root1, 'foo404' ) ).to.be.false;
  741. } );
  742. it( 'does not break when trying to check registered child in a context which contains unregistered elements', () => {
  743. const foo404 = new Element( 'foo404' );
  744. root1.appendChildren( foo404 );
  745. schema.register( '$root' );
  746. schema.register( '$text', {
  747. allowIn: '$root'
  748. } );
  749. expect( schema.checkChild( foo404, '$text' ) ).to.be.false;
  750. } );
  751. it( 'does not break when used allowedIn pointing to an unregistered element', () => {
  752. schema.register( '$root' );
  753. schema.register( '$text', {
  754. allowIn: 'foo404'
  755. } );
  756. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  757. } );
  758. it( 'does not break when used allowWhere pointing to an unregistered element', () => {
  759. schema.register( '$root' );
  760. schema.register( '$text', {
  761. allowWhere: 'foo404'
  762. } );
  763. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  764. } );
  765. it( 'does not break when used allowContentOf pointing to an unregistered element', () => {
  766. schema.register( '$root', {
  767. allowContentOf: 'foo404'
  768. } );
  769. schema.register( '$text', {
  770. allowIn: '$root'
  771. } );
  772. expect( schema.checkChild( root1, '$text' ) ).to.be.true;
  773. } );
  774. it( 'checks whether allowIn uses a registered element', () => {
  775. schema.register( 'paragraph', {
  776. allowIn: '$root'
  777. } );
  778. // $root isn't registered!
  779. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.false;
  780. } );
  781. it( 'does not break when inheriting all from an unregistered element', () => {
  782. schema.register( 'paragraph', {
  783. inheritAllFrom: '$block'
  784. } );
  785. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  786. } );
  787. } );
  788. describe( 'allowAttributes', () => {
  789. it( 'passes paragraph[align]', () => {
  790. schema.register( 'paragraph', {
  791. allowAttributes: 'align'
  792. } );
  793. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  794. } );
  795. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  796. schema.register( 'paragraph', {
  797. allowAttributes: [ 'align', 'dir' ]
  798. } );
  799. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  800. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  801. } );
  802. it( 'passes paragraph>$text[bold]', () => {
  803. schema.register( 'paragraph' );
  804. schema.register( '$text', {
  805. allowIn: 'paragraph',
  806. allowAttributes: 'bold'
  807. } );
  808. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  809. } );
  810. } );
  811. describe( 'allowAttributesOf', () => {
  812. it( 'passes paragraph[align] – paragraph inherits from $block', () => {
  813. schema.register( '$block', {
  814. allowAttributes: 'align'
  815. } );
  816. schema.register( 'paragraph', {
  817. allowAttributesOf: '$block'
  818. } );
  819. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  820. } );
  821. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  822. schema.register( '$block', {
  823. allowAttributes: 'align'
  824. } );
  825. schema.register( '$block2', {
  826. allowAttributes: 'dir'
  827. } );
  828. schema.register( 'paragraph', {
  829. allowAttributesOf: [ '$block', '$block2' ]
  830. } );
  831. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  832. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  833. } );
  834. it( 'passes paragraph[align] and paragraph[dir] – support for combined allowAttributes and allowAttributesOf', () => {
  835. schema.register( '$block', {
  836. allowAttributes: 'align'
  837. } );
  838. schema.register( 'paragraph', {
  839. allowAttributes: 'dir',
  840. allowAttributesOf: '$block'
  841. } );
  842. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  843. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  844. } );
  845. // The support for allowAttributesOf is broken in the similar way as for allowContentOf (see the comment above).
  846. // However, those situations are rather theoretical, so we're not going to waste time on them now.
  847. } );
  848. describe( 'inheritAllFrom', () => {
  849. it( 'passes paragraph[align] – paragraph inherits attributes of $block', () => {
  850. schema.register( '$block', {
  851. allowAttributes: 'align'
  852. } );
  853. schema.register( 'paragraph', {
  854. inheritAllFrom: '$block'
  855. } );
  856. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  857. } );
  858. it( 'passes paragraph[align] – paragraph inherits attributes of $block through allowAttributesOf', () => {
  859. schema.register( '$blockProto', {
  860. allowAttributes: 'align'
  861. } );
  862. schema.register( '$block', {
  863. allowAttributesOf: '$blockProto'
  864. } );
  865. schema.register( 'paragraph', {
  866. inheritAllFrom: '$block'
  867. } );
  868. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  869. } );
  870. } );
  871. describe( 'missing attribute rules', () => {
  872. it( 'does not crash when checking an attribute of a unregistered element', () => {
  873. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.false;
  874. } );
  875. it( 'does not crash when inheriting attributes of a unregistered element', () => {
  876. schema.register( 'paragraph', {
  877. allowAttributesOf: '$block'
  878. } );
  879. expect( schema.checkAttribute( r1p1, 'whatever' ) ).to.be.false;
  880. } );
  881. it( 'does not crash when inheriting all from a unregistered element', () => {
  882. schema.register( 'paragraph', {
  883. allowAttributesOf: '$block'
  884. } );
  885. expect( schema.checkAttribute( r1p1, 'whatever' ) ).to.be.false;
  886. } );
  887. } );
  888. } );
  889. describe( 'real scenarios', () => {
  890. let r1bQi, r1i, r1lI, r1h, r1bQlI;
  891. const rules = [
  892. () => {
  893. schema.register( 'paragraph', {
  894. allowWhere: '$block',
  895. allowContentOf: '$block',
  896. allowAttributesOf: '$block'
  897. } );
  898. },
  899. () => {
  900. schema.register( 'heading1', {
  901. allowWhere: '$block',
  902. allowContentOf: '$block',
  903. allowAttributesOf: '$block'
  904. } );
  905. },
  906. () => {
  907. schema.register( 'listItem', {
  908. allowWhere: '$block',
  909. allowContentOf: '$block',
  910. allowAttributes: [ 'indent', 'type' ],
  911. allowAttributesOf: '$block'
  912. } );
  913. },
  914. () => {
  915. schema.register( 'blockQuote', {
  916. allowWhere: '$block',
  917. allowContentOf: '$root'
  918. } );
  919. // Disallow blockQuote in blockQuote.
  920. schema.on( 'checkChild', ( evt, args ) => {
  921. const context = args[ 0 ];
  922. const child = args[ 1 ];
  923. const childRule = schema.getRule( child );
  924. if ( childRule.name == 'blockQuote' && context[ context.length - 1 ].name == 'blockQuote' ) {
  925. evt.stop();
  926. evt.return = false;
  927. }
  928. }, { priority: 'high' } );
  929. },
  930. () => {
  931. schema.register( 'image', {
  932. allowWhere: '$block',
  933. allowAttributes: [ 'src', 'alt' ]
  934. } );
  935. },
  936. () => {
  937. schema.register( 'caption', {
  938. allowIn: 'image',
  939. allowContentOf: '$block'
  940. } );
  941. },
  942. () => {
  943. schema.extend( '$text', {
  944. allowAttributes: [ 'bold', 'italic' ]
  945. } );
  946. // Disallow bold in heading1.
  947. schema.on( 'checkAttribute', ( evt, args ) => {
  948. const context = args[ 0 ];
  949. const contextLast = context[ context.length - 1 ];
  950. const contextSecondToLast = context[ context.length - 2 ];
  951. const attributeName = args[ 1 ];
  952. if ( contextLast.name == '$text' && contextSecondToLast.name == 'heading1' && attributeName == 'bold' ) {
  953. evt.stop();
  954. evt.return = false;
  955. }
  956. }, { priority: 'high' } );
  957. },
  958. () => {
  959. schema.extend( '$block', {
  960. allowAttributes: 'alignment'
  961. } );
  962. }
  963. ];
  964. beforeEach( () => {
  965. schema.register( '$root' );
  966. schema.register( '$block', {
  967. allowIn: '$root'
  968. } );
  969. schema.register( '$text', {
  970. allowIn: '$block'
  971. } );
  972. for ( const rule of rules ) {
  973. rule();
  974. }
  975. // or...
  976. //
  977. // Use the below code to shuffle the rules.
  978. // Don't look here, Szymon!
  979. //
  980. // const rulesCopy = rules.slice();
  981. //
  982. // while ( rulesCopy.length ) {
  983. // const r = Math.floor( Math.random() * rulesCopy.length );
  984. // rulesCopy.splice( r, 1 )[ 0 ]();
  985. // }
  986. root1 = new Element( '$root', null, [
  987. new Element( 'paragraph', null, 'foo' ),
  988. new Element( 'paragraph', { alignment: 'right' }, 'bar' ),
  989. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  990. new Element( 'heading1', null, 'foo' ),
  991. new Element( 'blockQuote', null, [
  992. new Element( 'paragraph', null, 'foo' ),
  993. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  994. new Element( 'image', null, [
  995. new Element( 'caption', null, 'foo' )
  996. ] )
  997. ] ),
  998. new Element( 'image', null, [
  999. new Element( 'caption', null, 'foo' )
  1000. ] )
  1001. ] );
  1002. r1p1 = root1.getChild( 0 );
  1003. r1p2 = root1.getChild( 1 );
  1004. r1lI = root1.getChild( 2 );
  1005. r1h = root1.getChild( 3 );
  1006. r1bQ = root1.getChild( 4 );
  1007. r1i = root1.getChild( 5 );
  1008. r1bQp = r1bQ.getChild( 0 );
  1009. r1bQlI = r1bQ.getChild( 1 );
  1010. r1bQi = r1bQ.getChild( 2 );
  1011. } );
  1012. it( 'passes $root>paragraph', () => {
  1013. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.true;
  1014. } );
  1015. it( 'passes $root>paragraph>$text', () => {
  1016. expect( schema.checkChild( r1p1, '$text' ), 'paragraph' ).to.be.true;
  1017. expect( schema.checkChild( r1p2, '$text' ), 'paragraph[alignment]' ).to.be.true;
  1018. } );
  1019. it( 'passes $root>listItem', () => {
  1020. expect( schema.checkChild( root1, 'listItem' ) ).to.be.true;
  1021. } );
  1022. it( 'passes $root>listItem>$text', () => {
  1023. expect( schema.checkChild( r1lI, '$text' ) ).to.be.true;
  1024. } );
  1025. it( 'passes $root>blockQuote>paragraph', () => {
  1026. expect( schema.checkChild( r1bQ, 'paragraph' ) ).to.be.true;
  1027. } );
  1028. it( 'passes $root>blockQuote>paragraph>$text', () => {
  1029. expect( schema.checkChild( r1bQp, '$text' ) ).to.be.true;
  1030. } );
  1031. it( 'passes $root>blockQuote>listItem', () => {
  1032. expect( schema.checkChild( r1bQ, 'listItem' ) ).to.be.true;
  1033. } );
  1034. it( 'passes $root>blockQuote>listItem>$text', () => {
  1035. expect( schema.checkChild( r1bQlI, '$text' ) ).to.be.true;
  1036. } );
  1037. it( 'passes $root>blockQuote>image', () => {
  1038. expect( schema.checkChild( r1bQ, 'image' ) ).to.be.true;
  1039. } );
  1040. it( 'passes $root>blockQuote>image>caption', () => {
  1041. expect( schema.checkChild( r1bQi, 'caption' ) ).to.be.true;
  1042. } );
  1043. it( 'passes $root>blockQuote>image>caption>$text', () => {
  1044. expect( schema.checkChild( r1bQi.getChild( 0 ), '$text' ) ).to.be.true;
  1045. } );
  1046. it( 'passes $root>image', () => {
  1047. expect( schema.checkChild( root1, 'image' ) ).to.be.true;
  1048. } );
  1049. it( 'passes $root>image>caption', () => {
  1050. expect( schema.checkChild( r1i, 'caption' ) ).to.be.true;
  1051. } );
  1052. it( 'passes $root>image>caption>$text', () => {
  1053. expect( schema.checkChild( r1i.getChild( 0 ), '$text' ) ).to.be.true;
  1054. } );
  1055. it( 'rejects $root>$root', () => {
  1056. expect( schema.checkChild( root1, '$root' ) ).to.be.false;
  1057. } );
  1058. it( 'rejects $root>$text', () => {
  1059. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  1060. } );
  1061. it( 'rejects $root>caption', () => {
  1062. expect( schema.checkChild( root1, 'caption' ) ).to.be.false;
  1063. } );
  1064. it( 'rejects $root>paragraph>paragraph', () => {
  1065. expect( schema.checkChild( r1p1, 'paragraph' ) ).to.be.false;
  1066. } );
  1067. it( 'rejects $root>paragraph>paragraph>$text', () => {
  1068. // Edge case because p>p should not exist in the first place.
  1069. // But it's good to know that it blocks also this.
  1070. const p = new Element( 'p' );
  1071. r1p1.appendChildren( p );
  1072. expect( schema.checkChild( p, '$text' ) ).to.be.false;
  1073. } );
  1074. it( 'rejects $root>paragraph>$block', () => {
  1075. expect( schema.checkChild( r1p1, '$block' ) ).to.be.false;
  1076. } );
  1077. it( 'rejects $root>paragraph>blockQuote', () => {
  1078. expect( schema.checkChild( r1p1, 'blockQuote' ) ).to.be.false;
  1079. } );
  1080. it( 'rejects $root>paragraph>image', () => {
  1081. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  1082. } );
  1083. it( 'rejects $root>paragraph>caption', () => {
  1084. expect( schema.checkChild( r1p1, 'caption' ) ).to.be.false;
  1085. } );
  1086. it( 'rejects $root>blockQuote>blockQuote', () => {
  1087. expect( schema.checkChild( r1bQ, 'blockQuote' ) ).to.be.false;
  1088. } );
  1089. it( 'rejects $root>blockQuote>caption', () => {
  1090. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  1091. } );
  1092. it( 'rejects $root>blockQuote>$text', () => {
  1093. expect( schema.checkChild( r1bQ, '$text' ) ).to.be.false;
  1094. } );
  1095. it( 'rejects $root>image>$text', () => {
  1096. expect( schema.checkChild( r1i, '$text' ) ).to.be.false;
  1097. } );
  1098. it( 'rejects $root>image>paragraph', () => {
  1099. expect( schema.checkChild( r1i, 'paragraph' ) ).to.be.false;
  1100. } );
  1101. it( 'rejects $root>image>caption>paragraph', () => {
  1102. expect( schema.checkChild( r1i.getChild( 0 ), 'paragraph' ) ).to.be.false;
  1103. } );
  1104. it( 'rejects $root>image>caption>blockQuote', () => {
  1105. expect( schema.checkChild( r1i.getChild( 0 ), 'blockQuote' ) ).to.be.false;
  1106. } );
  1107. it( 'accepts attribute $root>paragraph[alignment]', () => {
  1108. expect( schema.checkAttribute( r1p1, 'alignment' ) ).to.be.true;
  1109. } );
  1110. it( 'accepts attribute $root>paragraph>$text[bold]', () => {
  1111. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  1112. } );
  1113. it( 'accepts attribute $root>heading1>$text[italic]', () => {
  1114. expect( schema.checkAttribute( r1h.getChild( 0 ), 'italic' ) ).to.be.true;
  1115. } );
  1116. it( 'accepts attribute $root>blockQuote>paragraph>$text[bold]', () => {
  1117. expect( schema.checkAttribute( r1bQp.getChild( 0 ), 'bold' ) ).to.be.true;
  1118. } );
  1119. it( 'accepts attribute $root>listItem[alignment]', () => {
  1120. expect( schema.checkAttribute( r1lI, 'alignment' ) ).to.be.true;
  1121. } );
  1122. it( 'accepts attribute $root>listItem[indent]', () => {
  1123. expect( schema.checkAttribute( r1lI, 'indent' ) ).to.be.true;
  1124. } );
  1125. it( 'accepts attribute $root>listItem[type]', () => {
  1126. expect( schema.checkAttribute( r1lI, 'type' ) ).to.be.true;
  1127. } );
  1128. it( 'accepts attribute $root>image[src]', () => {
  1129. expect( schema.checkAttribute( r1i, 'src' ) ).to.be.true;
  1130. } );
  1131. it( 'accepts attribute $root>image[alt]', () => {
  1132. expect( schema.checkAttribute( r1i, 'alt' ) ).to.be.true;
  1133. } );
  1134. it( 'accepts attribute $root>image>caption>$text[bold]', () => {
  1135. expect( schema.checkAttribute( r1i.getChild( 0 ).getChild( 0 ), 'bold' ) ).to.be.true;
  1136. } );
  1137. it( 'rejects attribute $root[indent]', () => {
  1138. expect( schema.checkAttribute( root1, 'indent' ) ).to.be.false;
  1139. } );
  1140. it( 'rejects attribute $root>paragraph[indent]', () => {
  1141. expect( schema.checkAttribute( r1p1, 'indent' ) ).to.be.false;
  1142. } );
  1143. it( 'accepts attribute $root>heading1>$text[bold]', () => {
  1144. expect( schema.checkAttribute( r1h.getChild( 0 ), 'bold' ) ).to.be.false;
  1145. } );
  1146. it( 'rejects attribute $root>paragraph>$text[alignment]', () => {
  1147. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'alignment' ) ).to.be.false;
  1148. } );
  1149. it( 'rejects attribute $root>blockQuote[indent]', () => {
  1150. expect( schema.checkAttribute( r1bQ, 'indent' ) ).to.be.false;
  1151. } );
  1152. it( 'rejects attribute $root>blockQuote[alignment]', () => {
  1153. expect( schema.checkAttribute( r1bQ, 'alignment' ) ).to.be.false;
  1154. } );
  1155. it( 'rejects attribute $root>image[indent]', () => {
  1156. expect( schema.checkAttribute( r1i, 'indent' ) ).to.be.false;
  1157. } );
  1158. it( 'rejects attribute $root>image[alignment]', () => {
  1159. expect( schema.checkAttribute( r1i, 'alignment' ) ).to.be.false;
  1160. } );
  1161. } );
  1162. // TODO:
  1163. // * getValidRanges
  1164. // * checkAttributeInSelection
  1165. // * getLimitElement
  1166. // * removeDisallowedAttributes
  1167. // * test checkChild()'s both params normalization
  1168. // * and see insertContent's _checkIsObject()
  1169. // * add normalization to isObject(), isLimit(), isBlock(), isRegistered() and improve the existing code
  1170. // * inheritAllFrom should also inherit is* props (see tests documentselection getNearestSelectionRange())
  1171. // * test the default abstract entities (in model.js)
  1172. // * see clipboardHolder definition (and rename it to the pastebin)
  1173. // * review insertContent's _tryAutoparagraphing()
  1174. // * it doesn't make sense for VCD to get schema as a param (it can get it from the model)
  1175. // * V->M conversion tests might got outdated and would need to be reviewed if someone has a spare week ;)
  1176. // * Do we need both $inline and $text?
  1177. } );