488 lines
22 KiB
C
488 lines
22 KiB
C
/* Modelo de documento, sesiones de edición e historial. */
|
|
#include <dirent.h>
|
|
#include "tgb_test.h"
|
|
|
|
static tgb_context *g_ctx;
|
|
static char g_root[200];
|
|
|
|
static tgb_document *new_doc(int w, int h, int fps) {
|
|
tgb_document *d = NULL;
|
|
CHECK_OK(tgb_document_create(g_ctx, w, h, fps, &d));
|
|
return d;
|
|
}
|
|
static tgb_status paint_block(tgb_document *doc, size_t f, size_t l, int x, int y, int size, tgb_color col) {
|
|
tgb_status s = tgb_edit_begin(doc, tgb_frame_id(doc, f), tgb_layer_id(doc, l), "Bloque");
|
|
if (s != TGB_OK) return s;
|
|
tgb_canvas_fill_rect(tgb_edit_canvas(doc), (tgb_rect){x, y, size, size}, col, false);
|
|
return tgb_edit_end(doc);
|
|
}
|
|
static size_t undo_count(const tgb_document *d) { tgb_history_stats s; tgb_history_get_stats(d, &s); return s.undo_count; }
|
|
|
|
TEST(document_create_and_props) {
|
|
tgb_document *d = NULL;
|
|
CHECK_STATUS(tgb_document_create(NULL, 10, 10, 12, &d), TGB_ERR_INVALID_ARG);
|
|
CHECK_STATUS(tgb_document_create(g_ctx, 0, 10, 12, &d), TGB_ERR_RANGE);
|
|
CHECK_STATUS(tgb_document_create(g_ctx, 10, 10, 0, &d), TGB_ERR_RANGE);
|
|
CHECK_STATUS(tgb_document_create(g_ctx, 10, 10, 61, &d), TGB_ERR_RANGE);
|
|
CHECK(d == NULL);
|
|
d = new_doc(64, 48, 12);
|
|
CHECK_EQ(tgb_document_width(d), 64);
|
|
CHECK_EQ(tgb_document_height(d), 48);
|
|
CHECK_EQ(tgb_document_fps(d), 12);
|
|
CHECK_EQ(tgb_frame_count(d), 1);
|
|
CHECK_EQ(tgb_layer_count(d), 1);
|
|
CHECK(!strcmp(tgb_layer_name(d, 0), "Capa 1"));
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 0), 83);
|
|
CHECK(tgb_frame_cel(d, 0, 0) == NULL); /* celda vacía: no gasta memoria */
|
|
CHECK(!tgb_document_dirty(d));
|
|
CHECK(tgb_document_is_consistent(d));
|
|
CHECK(tgb_document_path(d) == NULL);
|
|
CHECK_OK(tgb_document_set_path(d, "/tmp/x.tgb"));
|
|
CHECK(!strcmp(tgb_document_path(d), "/tmp/x.tgb"));
|
|
CHECK_OK(paint_px(d, 0, 0, 1, 1, RED));
|
|
CHECK(tgb_document_dirty(d));
|
|
tgb_document_mark_saved(d);
|
|
CHECK(!tgb_document_dirty(d));
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK(tgb_document_dirty(d)); /* deshacer también cambia el documento */
|
|
tgb_document_destroy(d);
|
|
tgb_document_destroy(NULL);
|
|
}
|
|
|
|
TEST(edit_session_undo_redo) {
|
|
tgb_document *d = new_doc(32, 32, 12);
|
|
CHECK(!tgb_history_can_undo(d));
|
|
CHECK_STATUS(tgb_history_undo(d), TGB_ERR_NOTHING);
|
|
CHECK_STATUS(tgb_history_redo(d), TGB_ERR_NOTHING);
|
|
CHECK_OK(paint_px(d, 0, 0, 3, 4, RED));
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 3, 4, RED));
|
|
CHECK(tgb_history_can_undo(d));
|
|
CHECK(!strcmp(tgb_history_undo_label(d), "Punto"));
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(px_at(d, 0, 0, 3, 4).a, 0);
|
|
CHECK(tgb_history_can_redo(d));
|
|
CHECK(!strcmp(tgb_history_redo_label(d), "Punto"));
|
|
CHECK_OK(tgb_history_redo(d));
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 3, 4, RED));
|
|
|
|
/* una sesión sin cambios reales no crea operación */
|
|
size_t before = undo_count(d);
|
|
CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "Nada"));
|
|
tgb_canvas_set(tgb_edit_canvas(d), 3, 4, RED); /* mismo valor */
|
|
CHECK_OK(tgb_edit_end(d));
|
|
CHECK_EQ(undo_count(d), before);
|
|
|
|
/* estados inválidos */
|
|
CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "A"));
|
|
CHECK(tgb_edit_active(d));
|
|
CHECK_STATUS(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "B"), TGB_ERR_STATE);
|
|
CHECK_STATUS(tgb_history_undo(d), TGB_ERR_STATE);
|
|
CHECK_STATUS(tgb_frame_add(d, 1, NULL), TGB_ERR_STATE);
|
|
CHECK_STATUS(tgb_layer_add(d, 1, "x", NULL), TGB_ERR_STATE);
|
|
/* cancelar devuelve la celda a como estaba */
|
|
tgb_canvas_set(tgb_edit_canvas(d), 10, 10, BLUE);
|
|
CHECK_OK(tgb_edit_cancel(d));
|
|
CHECK_EQ(px_at(d, 0, 0, 10, 10).a, 0);
|
|
CHECK(!tgb_edit_active(d));
|
|
CHECK_STATUS(tgb_edit_end(d), TGB_ERR_STATE);
|
|
CHECK_STATUS(tgb_edit_cancel(d), TGB_ERR_STATE);
|
|
CHECK(tgb_edit_canvas(d) == NULL);
|
|
CHECK_STATUS(tgb_edit_begin(d, 9999, tgb_layer_id(d, 0), "x"), TGB_ERR_NOT_FOUND);
|
|
|
|
/* reset = volver al inicio de la sesión (vista previa de formas) */
|
|
CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "Línea"));
|
|
tgb_brush pen = tgb_brush_pencil(GREEN, 1);
|
|
tgb_draw_line(tgb_edit_canvas(d), &pen, 0, 20, 10, 20);
|
|
CHECK_OK(tgb_edit_reset(d));
|
|
tgb_draw_line(tgb_edit_canvas(d), &pen, 0, 25, 10, 25);
|
|
CHECK_OK(tgb_edit_end(d));
|
|
CHECK_EQ(px_at(d, 0, 0, 5, 20).a, 0);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 5, 25, GREEN));
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(locked_layer_rejects_edits) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(tgb_layer_set_locked(d, 0, true));
|
|
CHECK_STATUS(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "x"), TGB_ERR_LOCKED);
|
|
CHECK(!tgb_edit_active(d));
|
|
CHECK_OK(tgb_history_undo(d)); /* desbloquear por deshacer */
|
|
CHECK_OK(paint_px(d, 0, 0, 1, 1, RED));
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(multiple_strokes_and_redo_cleared) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
for (int i = 0; i < 3; i++) CHECK_OK(paint_px(d, 0, 0, i, 0, RED));
|
|
CHECK_EQ(undo_count(d), 3);
|
|
CHECK_OK(tgb_history_undo(d)); CHECK_OK(tgb_history_undo(d));
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 0, 0, RED));
|
|
CHECK_EQ(px_at(d, 0, 0, 1, 0).a, 0);
|
|
CHECK(tgb_history_can_redo(d));
|
|
CHECK_OK(paint_px(d, 0, 0, 9, 9, BLUE)); /* una acción nueva invalida el "rehacer" */
|
|
CHECK(!tgb_history_can_redo(d));
|
|
CHECK_STATUS(tgb_history_redo(d), TGB_ERR_NOTHING);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(frame_operations) {
|
|
tgb_document *d = new_doc(16, 16, 10);
|
|
uint32_t id1 = tgb_frame_id(d, 0), id2 = 0, id3 = 0;
|
|
CHECK_OK(tgb_frame_add(d, 1, &id2));
|
|
CHECK_OK(tgb_frame_add(d, 0, &id3));
|
|
CHECK_EQ(tgb_frame_count(d), 3);
|
|
CHECK(tgb_frame_id(d, 0) == id3 && tgb_frame_id(d, 1) == id1 && tgb_frame_id(d, 2) == id2);
|
|
size_t idx = 99;
|
|
CHECK_OK(tgb_frame_index_of(d, id2, &idx));
|
|
CHECK_EQ(idx, 2);
|
|
CHECK_STATUS(tgb_frame_index_of(d, 424242, &idx), TGB_ERR_NOT_FOUND);
|
|
CHECK_STATUS(tgb_frame_add(d, 9, NULL), TGB_ERR_RANGE);
|
|
CHECK_OK(tgb_frame_move(d, 0, 2));
|
|
CHECK(tgb_frame_id(d, 2) == id3 && tgb_frame_id(d, 0) == id1);
|
|
CHECK_STATUS(tgb_frame_move(d, 0, 7), TGB_ERR_RANGE);
|
|
CHECK_OK(tgb_history_undo(d)); /* deshacer el movimiento */
|
|
CHECK(tgb_frame_id(d, 0) == id3 && tgb_frame_id(d, 2) == id2);
|
|
CHECK_OK(tgb_frame_delete(d, 1));
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(tgb_frame_count(d), 3);
|
|
CHECK(tgb_frame_id(d, 1) == id1);
|
|
CHECK_OK(tgb_history_redo(d));
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK_OK(tgb_frame_delete(d, 0));
|
|
CHECK_STATUS(tgb_frame_delete(d, 0), TGB_ERR_STATE); /* nunca el último */
|
|
/* duración */
|
|
CHECK_STATUS(tgb_frame_set_duration(d, 0, 5), TGB_ERR_RANGE);
|
|
CHECK_STATUS(tgb_frame_set_duration(d, 0, 70000), TGB_ERR_RANGE);
|
|
CHECK_STATUS(tgb_frame_set_duration(d, 5, 100), TGB_ERR_RANGE);
|
|
CHECK_OK(tgb_frame_set_duration(d, 0, 250));
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 0), 250);
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 0), 100);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(duplicate_shares_memory_until_edited) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(paint_px(d, 0, 0, 2, 2, RED));
|
|
CHECK_OK(tgb_frame_duplicate(d, 0, NULL));
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK(tgb_frame_cel(d, 0, 0) == tgb_frame_cel(d, 1, 0)); /* misma celda en memoria */
|
|
CHECK_EQ(tgb_canvas_refs(tgb_frame_cel(d, 0, 0)), 2);
|
|
CHECK_OK(paint_px(d, 1, 0, 5, 5, BLUE)); /* copy-on-write */
|
|
CHECK(tgb_frame_cel(d, 0, 0) != tgb_frame_cel(d, 1, 0));
|
|
CHECK_EQ(px_at(d, 0, 0, 5, 5).a, 0); /* el original NO cambia */
|
|
CHECK(is_px(tgb_frame_cel(d, 1, 0), 2, 2, RED) && is_px(tgb_frame_cel(d, 1, 0), 5, 5, BLUE));
|
|
CHECK_OK(tgb_history_undo(d)); /* deshacer la edición de la copia */
|
|
CHECK_EQ(px_at(d, 1, 0, 5, 5).a, 0);
|
|
CHECK_OK(tgb_history_undo(d)); /* deshacer la duplicación */
|
|
CHECK_EQ(tgb_frame_count(d), 1);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 2, 2, RED));
|
|
CHECK_OK(tgb_history_redo(d)); CHECK_OK(tgb_history_redo(d));
|
|
CHECK(is_px(tgb_frame_cel(d, 1, 0), 5, 5, BLUE));
|
|
CHECK_EQ(px_at(d, 0, 0, 5, 5).a, 0);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(delete_frame_keeps_pixels_for_undo) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
CHECK_OK(paint_px(d, 1, 0, 7, 7, GREEN));
|
|
CHECK_OK(tgb_frame_delete(d, 1));
|
|
CHECK_EQ(tgb_frame_count(d), 1);
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK(is_px(tgb_frame_cel(d, 1, 0), 7, 7, GREEN));
|
|
CHECK_OK(tgb_history_undo(d)); /* deshacer el dibujo en el fotograma restaurado */
|
|
CHECK_EQ(px_at(d, 1, 0, 7, 7).a, 0);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(layer_operations) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
uint32_t lid = 0;
|
|
CHECK_OK(tgb_layer_add(d, 1, "Tinta", &lid));
|
|
CHECK_EQ(tgb_layer_count(d), 2);
|
|
CHECK(!strcmp(tgb_layer_name(d, 1), "Tinta"));
|
|
CHECK(tgb_layer_visible(d, 1) && !tgb_layer_locked(d, 1) && tgb_layer_opacity(d, 1) == 255);
|
|
CHECK_STATUS(tgb_layer_add(d, 1, "", NULL), TGB_ERR_INVALID_ARG);
|
|
CHECK_STATUS(tgb_layer_add(d, 1, "\xff\xfe", NULL), TGB_ERR_INVALID_ARG); /* UTF-8 inválido */
|
|
char longname[200]; memset(longname, 'a', sizeof longname - 1); longname[sizeof longname - 1] = 0;
|
|
CHECK_STATUS(tgb_layer_add(d, 1, longname, NULL), TGB_ERR_LIMIT);
|
|
CHECK_STATUS(tgb_layer_add(d, 9, "x", NULL), TGB_ERR_RANGE);
|
|
CHECK_OK(paint_px(d, 0, 1, 4, 4, RED));
|
|
CHECK_OK(paint_px(d, 1, 1, 6, 6, BLUE));
|
|
CHECK_OK(tgb_layer_move(d, 1, 0));
|
|
CHECK(tgb_layer_id(d, 0) == lid);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 4, 4, RED)); /* las celdas se mueven con su capa */
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK(tgb_layer_id(d, 1) == lid && is_px(tgb_frame_cel(d, 0, 1), 4, 4, RED));
|
|
CHECK_OK(tgb_layer_rename(d, 1, "Color"));
|
|
CHECK(!strcmp(tgb_layer_name(d, 1), "Color"));
|
|
CHECK_OK(tgb_layer_set_visible(d, 1, false));
|
|
CHECK_OK(tgb_layer_set_opacity(d, 1, 128));
|
|
CHECK(!tgb_layer_visible(d, 1) && tgb_layer_opacity(d, 1) == 128);
|
|
CHECK_OK(tgb_history_undo(d)); CHECK_OK(tgb_history_undo(d)); CHECK_OK(tgb_history_undo(d));
|
|
CHECK(!strcmp(tgb_layer_name(d, 1), "Tinta") && tgb_layer_visible(d, 1) && tgb_layer_opacity(d, 1) == 255);
|
|
CHECK_OK(tgb_history_redo(d));
|
|
CHECK(!strcmp(tgb_layer_name(d, 1), "Color"));
|
|
/* borrar capa con dibujos y deshacer */
|
|
CHECK_OK(tgb_layer_delete(d, 1));
|
|
CHECK_EQ(tgb_layer_count(d), 1);
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(tgb_layer_count(d), 2);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 1), 4, 4, RED) && is_px(tgb_frame_cel(d, 1, 1), 6, 6, BLUE));
|
|
CHECK_OK(tgb_layer_delete(d, 1));
|
|
CHECK_STATUS(tgb_layer_delete(d, 0), TGB_ERR_STATE); /* nunca la última */
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(fps_change_is_one_undo_step) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
size_t before = undo_count(d);
|
|
CHECK_OK(tgb_document_set_fps(d, 24, true));
|
|
CHECK_EQ(tgb_document_fps(d), 24);
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 0), 42);
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 1), 42);
|
|
CHECK_EQ(undo_count(d), before + 1);
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(tgb_document_fps(d), 12);
|
|
CHECK_EQ(tgb_frame_duration_ms(d, 1), 83);
|
|
CHECK_STATUS(tgb_document_set_fps(d, 0, true), TGB_ERR_RANGE);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(frame_clipboard) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_STATUS(tgb_frame_paste(d, 0, NULL), TGB_ERR_STATE); /* portapapeles vacío */
|
|
CHECK_OK(paint_px(d, 0, 0, 2, 3, RED));
|
|
CHECK_OK(tgb_frame_copy(d, 0));
|
|
uint32_t id = 0;
|
|
CHECK_OK(tgb_frame_paste(d, 1, &id));
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK(tgb_frame_id(d, 1) == id);
|
|
CHECK(is_px(tgb_frame_cel(d, 1, 0), 2, 3, RED));
|
|
tgb_document *other = new_doc(20, 20, 12); /* otras dimensiones */
|
|
CHECK_STATUS(tgb_frame_paste(other, 0, NULL), TGB_ERR_UNSUPPORTED);
|
|
CHECK_STATUS(tgb_frame_copy(d, 9), TGB_ERR_RANGE);
|
|
tgb_document_destroy(other);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(groups) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_STATUS(tgb_history_end_group(d), TGB_ERR_STATE);
|
|
CHECK_OK(tgb_history_begin_group(d, "Tres puntos"));
|
|
for (int i = 0; i < 3; i++) CHECK_OK(paint_px(d, 0, 0, i, 0, RED));
|
|
CHECK(!tgb_history_can_undo(d)); /* con un grupo abierto no se deshace */
|
|
CHECK_STATUS(tgb_history_undo(d), TGB_ERR_STATE);
|
|
CHECK_OK(tgb_history_end_group(d));
|
|
CHECK_EQ(undo_count(d), 1);
|
|
CHECK(!strcmp(tgb_history_undo_label(d), "Tres puntos"));
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(count_alpha(tgb_frame_cel(d, 0, 0)), 0);
|
|
CHECK_OK(tgb_history_redo(d));
|
|
CHECK_EQ(count_alpha(tgb_frame_cel(d, 0, 0)), 3);
|
|
/* anidados y mezclando estructura + píxeles */
|
|
CHECK_OK(tgb_history_begin_group(d, "Exterior"));
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
CHECK_OK(tgb_history_begin_group(d, "Interior"));
|
|
CHECK_OK(paint_px(d, 1, 0, 5, 5, BLUE));
|
|
CHECK_OK(tgb_layer_add(d, 1, "Extra", NULL));
|
|
CHECK_OK(tgb_history_end_group(d));
|
|
CHECK_OK(paint_px(d, 0, 1, 1, 1, GREEN));
|
|
CHECK_OK(tgb_history_end_group(d));
|
|
CHECK_EQ(undo_count(d), 2);
|
|
CHECK_EQ(tgb_frame_count(d), 2);
|
|
CHECK_EQ(tgb_layer_count(d), 2);
|
|
CHECK_OK(tgb_history_undo(d)); /* todo el grupo exterior de una vez */
|
|
CHECK_EQ(tgb_frame_count(d), 1);
|
|
CHECK_EQ(tgb_layer_count(d), 1);
|
|
CHECK_OK(tgb_history_redo(d));
|
|
CHECK(is_px(tgb_frame_cel(d, 1, 0), 5, 5, BLUE) && is_px(tgb_frame_cel(d, 0, 1), 1, 1, GREEN));
|
|
/* grupo vacío: no crea operación */
|
|
size_t n = undo_count(d);
|
|
CHECK_OK(tgb_history_begin_group(d, "Vacío"));
|
|
CHECK_OK(tgb_history_end_group(d));
|
|
CHECK_EQ(undo_count(d), n);
|
|
/* sesión de edición abierta impide cerrar el grupo */
|
|
CHECK_OK(tgb_history_begin_group(d, "G"));
|
|
CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "x"));
|
|
CHECK_STATUS(tgb_history_end_group(d), TGB_ERR_STATE);
|
|
CHECK_OK(tgb_edit_cancel(d));
|
|
CHECK_OK(tgb_history_end_group(d));
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(group_abort_restores_state) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
CHECK_OK(paint_px(d, 0, 0, 1, 1, RED));
|
|
size_t n = undo_count(d);
|
|
CHECK_OK(tgb_history_begin_group(d, "Fallido"));
|
|
CHECK_OK(paint_px(d, 0, 0, 2, 2, BLUE));
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
CHECK_OK(tgb_layer_add(d, 1, "L", NULL));
|
|
CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "pendiente")); /* sesión abierta a medias */
|
|
tgb_canvas_set(tgb_edit_canvas(d), 8, 8, GREEN);
|
|
CHECK_OK(tgb_history_abort_group(d));
|
|
CHECK(!tgb_edit_active(d));
|
|
CHECK_EQ(tgb_frame_count(d), 1);
|
|
CHECK_EQ(tgb_layer_count(d), 1);
|
|
CHECK_EQ(count_alpha(tgb_frame_cel(d, 0, 0)), 1);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 1, 1, RED));
|
|
CHECK_EQ(undo_count(d), n); /* el grupo abortado no queda en el historial */
|
|
CHECK(tgb_document_is_consistent(d));
|
|
CHECK_STATUS(tgb_history_abort_group(d), TGB_ERR_STATE);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(limit_operation_count) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
tgb_history_limits lim; tgb_history_get_limits(d, &lim);
|
|
lim.max_operations = 3;
|
|
CHECK_OK(tgb_history_set_limits(d, &lim));
|
|
for (int i = 0; i < 5; i++) CHECK_OK(paint_px(d, 0, 0, i, 0, RED));
|
|
tgb_history_stats st; tgb_history_get_stats(d, &st);
|
|
CHECK_EQ(st.undo_count, 3);
|
|
CHECK_EQ(st.dropped_ops, 2);
|
|
CHECK_OK(tgb_history_undo(d)); CHECK_OK(tgb_history_undo(d)); CHECK_OK(tgb_history_undo(d));
|
|
CHECK_STATUS(tgb_history_undo(d), TGB_ERR_NOTHING);
|
|
CHECK_EQ(count_alpha(tgb_frame_cel(d, 0, 0)), 2); /* lo más antiguo ya no se puede deshacer */
|
|
lim.max_operations = 0;
|
|
CHECK_STATUS(tgb_history_set_limits(d, &lim), TGB_ERR_RANGE);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(limit_memory_drops_oldest_without_disk) {
|
|
tgb_document *d = new_doc(256, 64, 12);
|
|
tgb_history_limits lim; tgb_history_get_limits(d, &lim);
|
|
lim.max_memory_bytes = 12000; lim.max_temp_bytes = 0; lim.temp_dir[0] = '\0';
|
|
CHECK_OK(tgb_history_set_limits(d, &lim));
|
|
for (int i = 0; i < 10; i++) CHECK_OK(paint_block(d, 0, 0, i * 24, 0, 20, RED)); /* ~3,3 KB cada una */
|
|
tgb_history_stats st; tgb_history_get_stats(d, &st);
|
|
CHECK(st.memory_bytes <= 12000);
|
|
CHECK(st.dropped_ops > 0 && st.undo_count + st.dropped_ops == 10);
|
|
size_t undone = 0;
|
|
while (tgb_history_undo(d) == TGB_OK) undone++;
|
|
CHECK_EQ(undone, st.undo_count);
|
|
/* una sola operación mayor que todo el presupuesto: se aplica, pero avisa de que no se puede deshacer */
|
|
lim.max_memory_bytes = 100;
|
|
CHECK_OK(tgb_history_set_limits(d, &lim));
|
|
CHECK_STATUS(paint_block(d, 0, 0, 0, 30, 20, BLUE), TGB_WARN_HISTORY_LOST);
|
|
CHECK(is_px(tgb_frame_cel(d, 0, 0), 5, 35, BLUE)); /* la edición SÍ se aplicó */
|
|
CHECK(!tgb_history_can_undo(d));
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
static size_t count_spill_files(const char *dir) {
|
|
DIR *dp = opendir(dir);
|
|
if (!dp) return 0;
|
|
size_t n = 0; struct dirent *e;
|
|
while ((e = readdir(dp))) if (!strncmp(e->d_name, "tgb-hist-", 9)) n++;
|
|
closedir(dp);
|
|
return n;
|
|
}
|
|
|
|
TEST(limit_memory_spills_to_disk_and_restores) {
|
|
tgb_document *d = new_doc(256, 64, 12);
|
|
char dir[500]; snprintf(dir, sizeof dir, "%s/spill", g_root);
|
|
tgb_history_limits lim; tgb_history_get_limits(d, &lim);
|
|
lim.max_memory_bytes = 10000;
|
|
lim.max_temp_bytes = 5u * 1024 * 1024;
|
|
snprintf(lim.temp_dir, sizeof lim.temp_dir, "%s", dir);
|
|
CHECK_OK(tgb_history_set_limits(d, &lim));
|
|
for (int i = 0; i < 10; i++) CHECK_OK(paint_block(d, 0, 0, i * 24, 0, 20, RED));
|
|
tgb_history_stats st; tgb_history_get_stats(d, &st);
|
|
CHECK(st.spilled_ops > 0);
|
|
CHECK(st.temp_bytes > 0);
|
|
CHECK(st.memory_bytes <= 10000);
|
|
CHECK_EQ(st.dropped_ops, 0); /* con disco disponible no se pierde nada */
|
|
CHECK_EQ(st.undo_count, 10);
|
|
CHECK(count_spill_files(dir) > 0);
|
|
for (int i = 9; i >= 0; i--) { /* deshacer TODO, incluidas las operaciones en disco */
|
|
CHECK_OK(tgb_history_undo(d));
|
|
CHECK_EQ(px_at(d, 0, 0, i * 24, 0).a, 0);
|
|
if (i > 0) CHECK(is_px(tgb_frame_cel(d, 0, 0), (i - 1) * 24, 0, RED));
|
|
}
|
|
for (int i = 0; i < 10; i++) CHECK_OK(tgb_history_redo(d));
|
|
CHECK_EQ(count_alpha(tgb_frame_cel(d, 0, 0)), 4000);
|
|
tgb_document_destroy(d);
|
|
CHECK_EQ(count_spill_files(dir), 0); /* no quedan archivos temporales */
|
|
}
|
|
|
|
TEST(limit_disk_exhausted_drops_oldest) {
|
|
tgb_document *d = new_doc(256, 64, 12);
|
|
char dir[500]; snprintf(dir, sizeof dir, "%s/spill2", g_root);
|
|
tgb_history_limits lim; tgb_history_get_limits(d, &lim);
|
|
lim.max_memory_bytes = 6000; lim.max_temp_bytes = 8000;
|
|
snprintf(lim.temp_dir, sizeof lim.temp_dir, "%s", dir);
|
|
CHECK_OK(tgb_history_set_limits(d, &lim));
|
|
for (int i = 0; i < 12; i++) CHECK_OK(paint_block(d, 0, 0, i * 17, 0, 16, RED));
|
|
tgb_history_stats st; tgb_history_get_stats(d, &st);
|
|
CHECK(st.memory_bytes <= 6000 && st.temp_bytes <= 8000);
|
|
CHECK(st.dropped_ops > 0);
|
|
CHECK(tgb_document_is_consistent(d));
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
typedef struct { int counts[16]; } event_counts;
|
|
static void on_event(const tgb_event *ev, void *user) { ((event_counts *)user)->counts[ev->type]++; }
|
|
|
|
TEST(events_are_emitted) {
|
|
tgb_document *d = new_doc(16, 16, 12);
|
|
event_counts ec; memset(&ec, 0, sizeof ec);
|
|
uint32_t sub = 0;
|
|
CHECK_OK(tgb_events_subscribe(g_ctx, on_event, &ec, &sub));
|
|
CHECK_OK(paint_px(d, 0, 0, 1, 1, RED));
|
|
CHECK(ec.counts[TGB_EVENT_PIXELS_CHANGED] >= 1 && ec.counts[TGB_EVENT_HISTORY_CHANGED] >= 1 && ec.counts[TGB_EVENT_DOCUMENT_CHANGED] >= 1);
|
|
CHECK_OK(tgb_frame_add(d, 1, NULL));
|
|
CHECK(ec.counts[TGB_EVENT_FRAMES_CHANGED] >= 1);
|
|
CHECK_OK(tgb_layer_add(d, 1, "L", NULL));
|
|
CHECK(ec.counts[TGB_EVENT_LAYERS_CHANGED] >= 1);
|
|
CHECK_OK(tgb_events_unsubscribe(g_ctx, sub));
|
|
int before = ec.counts[TGB_EVENT_PIXELS_CHANGED];
|
|
CHECK_OK(paint_px(d, 0, 0, 2, 2, RED));
|
|
CHECK_EQ(ec.counts[TGB_EVENT_PIXELS_CHANGED], before); /* ya no recibe */
|
|
CHECK_STATUS(tgb_events_unsubscribe(g_ctx, sub), TGB_ERR_NOT_FOUND);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
TEST(adaptive_limits) {
|
|
tgb_document *d = new_doc(960, 540, 12);
|
|
tgb_history_limits lim;
|
|
tgb_history_get_limits(d, &lim);
|
|
CHECK(lim.max_memory_bytes >= 960u * 540u * 4u * 8u); /* al menos 8 celdas completas */
|
|
CHECK(lim.max_operations >= 100);
|
|
CHECK_OK(tgb_history_adapt(d));
|
|
tgb_history_get_limits(d, &lim);
|
|
CHECK(lim.max_memory_bytes > 0);
|
|
tgb_document_destroy(d);
|
|
}
|
|
|
|
int main(void) {
|
|
g_ctx = tgb_test_context(g_root, sizeof g_root);
|
|
RUN(document_create_and_props);
|
|
RUN(edit_session_undo_redo);
|
|
RUN(locked_layer_rejects_edits);
|
|
RUN(multiple_strokes_and_redo_cleared);
|
|
RUN(frame_operations);
|
|
RUN(duplicate_shares_memory_until_edited);
|
|
RUN(delete_frame_keeps_pixels_for_undo);
|
|
RUN(layer_operations);
|
|
RUN(fps_change_is_one_undo_step);
|
|
RUN(frame_clipboard);
|
|
RUN(groups);
|
|
RUN(group_abort_restores_state);
|
|
RUN(limit_operation_count);
|
|
RUN(limit_memory_drops_oldest_without_disk);
|
|
RUN(limit_memory_spills_to_disk_and_restores);
|
|
RUN(limit_disk_exhausted_drops_oldest);
|
|
RUN(events_are_emitted);
|
|
RUN(adaptive_limits);
|
|
tgb_context_destroy(g_ctx);
|
|
tgb_test_rmtree(g_root);
|
|
TEST_SUMMARY();
|
|
}
|