Repository: 4ian/GDevelop
File: newIDE/app/src/Utils/ProjectSettingsReader.spec.js (JavaScript)

Code before:
    });

    test('ignores non-boolean values', () => {
      const rawSettings = {
        autosaveOnPreview: 'true',
        use3DEditor: 42,
        showDeprecatedInstructionWarning: null,
      };
      expect(convertToProjectSettings(rawSettings)).toEqual({});
    });

    test('handles all valid preference keys', () => {
      const rawSettings: { [string]: boolean } = {};
      VALID_PREFERENCE_KEYS.forEach(key => {
        rawSettings[key] = true;
      });

      const result = convertToProjectSettings(rawSettings);

      VALID_PREFERENCE_KEYS.forEach(key => {
        expect(result[key]).toBe(true);
      });
    });
  });

  describe('VALID_PREFERENCE_KEYS', () => {
    test('contains expected keys', () => {
      expect(VALID_PREFERENCE_KEYS.has('autosaveOnPreview')).toBe(true);
      expect(VALID_PREFERENCE_KEYS.has('use3DEditor')).toBe(true);
      expect(
        VALID_PREFERENCE_KEYS.has('showDeprecatedInstructionWarning')
      ).toBe(true);
      expect(VALID_PREFERENCE_KEYS.has('useGDJSDevelopmentWatcher')).toBe(true);
    });

    test('does not contain invalid keys', () => {
      expect(VALID_PREFERENCE_KEYS.has('invalidKey')).toBe(false);
      expect(VALID_PREFERENCE_KEYS.has('')).toBe(false);
    });
  });
});


Review comment (suggestion):
These tests are a bit of a "tautology". I would rather reduce the noise-to-signal ratio by not having them, as "testing a Set" is not super interesting and will actually make the codebase less flexible (because you have to remember updating these if at some point you rework the set to be something else). In other words, the Set is not really something I would expose to the "outside world".

I think the only test that would be worth having is one that the content from a file (Yaml file maybe, as discussed in my other comment) is properly applied to preferences.

Code after:
// @flow
import {
  filterAllowedPreferences,
  applyProjectSettings,
} from './ApplyProjectSettings';
import YAML from 'yaml';

describe('ProjectSettingsReader', () => {
  describe('end-to-end: YAML content applied to preferences', () => {
    test('gdevelop-settings.yaml content is properly parsed, filtered, and applied to preferences', () => {
      const yamlContent = `
# Project settings
preferences:
  # Boolean values
  autosaveOnPreview: true
  takeScreenshotOnPreview: false
  use3DEditor: true
  showBasicProfilingCounters: false
  showDeprecatedInstructionWarning: true
  # String values
  themeName: "Dark"
  language: "en"
  newProjectsDefaultFolder: "/path/to/projects"
  # Number values
  eventsSheetZoomLevel: 1.5
  eventsSheetIndentScale: 2
  # Unknown preference (not in allowlist)
  unknownPreference: true
  # Unsupported types (should be ignored by readProjectSettings)
  arrayValue:
    - item1
    - item2
  objectValue:
    nested: true
  nullValue: null
`;
      // Step 1: Parse YAML
      const parsed = YAML.parse(yamlContent);
      expect(parsed).toHaveProperty('preferences');

      // Step 2: Extract preferences section - includes all types from YAML
      const rawPreferences = parsed.preferences;
      expect(rawPreferences).toEqual({
        autosaveOnPreview: true,
        takeScreenshotOnPreview: false,
        use3DEditor: true,
        showBasicProfilingCounters: false,
        showDeprecatedInstructionWarning: true,
        themeName: 'Dark',
        language: 'en',
        newProjectsDefaultFolder: '/path/to/projects',
        eventsSheetZoomLevel: 1.5,
        eventsSheetIndentScale: 2,
        unknownPreference: true,
        arrayValue: ['item1', 'item2'],
        objectValue: { nested: true },
        nullValue: null,
      });

      // Step 3: Filter by allowlist (only allowed primitive types pass through)
      const filtered = filterAllowedPreferences(rawPreferences);
      expect(filtered).toEqual({
        autosaveOnPreview: true,
        takeScreenshotOnPreview: false,
        use3DEditor: true,
        showBasicProfilingCounters: false,
        showDeprecatedInstructionWarning: true,
        themeName: 'Dark',
        language: 'en',
        newProjectsDefaultFolder: '/path/to/projects',
        eventsSheetZoomLevel: 1.5,
        eventsSheetIndentScale: 2,
      });

      // Step 4: Apply to preferences via setMultipleValues
      const mockSetMultipleValues = jest.fn();
      const mockPreferences = {
        values: {},
        setMultipleValues: mockSetMultipleValues,
      };

      // $FlowFixMe - partial mock
      applyProjectSettings({ preferences: rawPreferences }, mockPreferences);

      expect(mockSetMultipleValues).toHaveBeenCalledWith({
        autosaveOnPreview: true,
        takeScreenshotOnPreview: false,
        use3DEditor: true,
        showBasicProfilingCounters: false,
        showDeprecatedInstructionWarning: true,
        themeName: 'Dark',
        language: 'en',
        newProjectsDefaultFolder: '/path/to/projects',
        eventsSheetZoomLevel: 1.5,
        eventsSheetIndentScale: 2,
      });
    });

    test('readProjectSettings would return null when preferences section is missing', () => {
      const yamlContent = `
# Project settings without preferences
someOtherSection:
  key: value
`;
      const parsed = YAML.parse(yamlContent);
      // readProjectSettings checks for preferences and returns null if missing
      expect(parsed.preferences).toBeUndefined();
    });

    test('readProjectSettings would return null when preferences section is empty', () => {
      const yamlContent = `
# Project settings with empty preferences
preferences:
`;
      const parsed = YAML.parse(yamlContent);
      // readProjectSettings checks for preferences and returns null if empty/null
      expect(parsed.preferences).toBeNull();
    });
  });
});