lively.ide_components_debug.js

import { num, promise, string, arr } from 'lively.lang';
import { Color, pt, Rectangle } from 'lively.graphics';
import { Point } from 'lively.graphics/geometry-2d.js';
import { ShadowObject, part } from 'lively.morphic';
import { module } from 'lively.modules/index.js';
import { browserForFile } from '../js/browser/ui.cp.js';
import { parse } from 'lively.ast';

/**
 * The following setup performs randomized operations on a component morph,
 * constantly altering the component definition. This allows us to capture
 * edge cases in the source ransformation that lead to unvalid syntax or
 * overly verbose component definitions.
 */

/**
 * Randomly selects a descendant of this morph, or the morph itself if no submorphs present;
 * @param { Morph } aMorph - The morph whose descendants to traverse.
 * @returns { Morph } The randomly selected morph.
 */
function randomSelectChild (aMorph) {
  if (aMorph.submorphs.length === 0) return aMorph;
  return arr.shuffle(aMorph.withAllSubmorphsDo((m) => m))[0];
}

function generateValueFor (propSpec) {
  let { defaultValue, type, values, min = 0, max = 100 } = propSpec;
  if (!defaultValue && !type) return;
  if (!type) {
    type = defaultValue.constructor?.name;
    if (!type) return;
  }
  switch (type) {
    case 'Boolean': return !!num.random(0, 1);
    case 'String': return string.newUUID();
    case 'Enum': return arr.shuffle(values)[0];
    case 'Color': return Color.random();
    // ColorGradient,
    // Layout,
    case 'Rectangle': return Rectangle.fromAny(Point.random(pt(500, 500)), Point.random(pt(500, 500)));
    case 'Number': return num.random(min, max) || num.random();
    case 'Shadow': return new ShadowObject({});
    case 'Point': return Point.random(pt(500, 500));
  }
}

/**
 * Generates a randomized set of properties that can be applied to the given morph.
 * @param { Morph } aMorph - The morph to generate the props for.
 */
function generatePropsFor (aMorph) {
  const props = aMorph.propertiesAndPropertySettings().properties;
  const { styleProperties } = aMorph;
  const selectedProps = arr.shuffle(styleProperties).slice(0, num.random(0, styleProperties.length / 4));
  const generated = {};
  for (let propName of selectedProps) {
    generated[propName] = generateValueFor(props[propName]);
    if (typeof generated[propName] === 'undefined') delete generated[propName];
  }
  return generated;
}

let editableComponent;
const testModuleId = 'local://lively-object-modules/Test/component-monkey-patch-test-a.cp.js';
const initSource = `
import { part, component, ComponentDescriptor } from 'lively.morphic/components/core.js';
import { InteractiveComponentDescriptor } from 'lively.ide/components/editor.js';
import { Color, pt} from 'lively.graphics';
import { Text } from "lively.morphic";

component.DescriptorClass = InteractiveComponentDescriptor;

const C = component({
  fill: Color.grey,
});

const D = component({
  fill: Color.purple,
  submorphs: [{
    name: 'a deep morph',
    fill: Color.orange
  }]
});

const A = component({
  fill: Color.red,
  extent: pt(100,100),
  submorphs: [{
    type: Text,
    name: 'some submorph',
    extent: pt(50,50),
    fixedWidth: true,
    fixedHeight: true,
    fill: Color.yellow,
  },part(D, { name: 'some ref'})]
});

const Monkey = component(A, {
  submorphs: [{
    name: 'some submorph',
    fill: Color.green
  }]
});

component.DescriptorClass = ComponentDescriptor;

export { A, C, D, Monkey };
`;

async function ensureModule () {
  let testComponentModule = module(testModuleId);

  await testComponentModule.reset();
  if (testComponentModule.format() === 'global') {
    await testComponentModule.changeSource('', { moduleId: testModuleId });
    await testComponentModule.reload();
    await testComponentModule.setFormat('register');
    await testComponentModule.changeSource(initSource, { moduleId: testModuleId });
    await testComponentModule.reload();
  } else {
    // reset the module to its original code
    await testComponentModule.changeSource(initSource, { moduleId: testModuleId });
  }
}

let Monkey, A, C, D;

async function createSetup () {
  await ensureModule();
  const testComponentModule = module(testModuleId);
  ({ Monkey, A, C, D } = await testComponentModule.load());
  editableComponent = await Monkey.edit();
  return await $world.execCommand('open browser', { moduleName: testModuleId, packageName: 'Test', reuse: true });
}

// performNextChange()

async function performNextChange () {
  // pick between adding a new morph,
  // removing a morph
  // or changing a prop
  const target = randomSelectChild(editableComponent);
  switch (arr.shuffle(['add', 'remove', 'prop'])[0]) {
    case 'add':
      let newChild = part(arr.shuffle([A, C, D])[0], { name: string.newUUID() });
      editableComponent.withMetaDo({ reconcileChanges: true }, () => {
        target.addMorph(newChild, arr.shuffle(target.submorphs)[0]);
      });
      return ['add', target, newChild];
      break;
    case 'remove':
      if (target !== editableComponent) {
        const ownerChain = target.ownerChain().map(m => m.name);
        editableComponent.withMetaDo({ reconcileChanges: true }, () => {
          target.remove();
        });
        return ['remove', target, ownerChain];
      }
    case 'prop':
      let props = generatePropsFor(target);
      editableComponent.withMetaDo({ reconcileChanges: true }, () => {
        Object.assign(target, props);
      });
      return ['apply props', target, props];
  }
}

// errorChange = await runSteps(1000);
// errorChange[1].name
// errorChange[2]
// errorChange[3]

async function runSteps (n) {
  let sourceBefore;
  const b = await createSetup();
  await b.whenRendered();
  const editor = b.get('source editor');
  editor.scrollPageDown();
  await b.whenRendered();
  while (n-- > 0) {
    await editor.whenRendered();
    sourceBefore = editor.textString;
    const change = await performNextChange();
    try {
      parse(editor.textString);
    } catch (err) {
      b.getWindow().remove();
      change.push(sourceBefore);
      change.push(editor.textString);
      return change;
    }
    if (editor.textString.match(/part\((A|B|C)\)/)) debugger;
  }
}